
How two programmers baked bread

I have been working as a programmer for many years, during which, oddly enough, I have been programming something all the time. And what an interesting thing I noticed: in the code that I wrote a month ago, I always want to fix something a bit. I would like to change a lot in the code of six months ago, and the code written two or three years ago turns me into emo: I want to cry and die. In this article I will describe two approaches. Thanks to the first, the architecture of the program is confusing, and the maintenance is unreasonably expensive, and the second is the KISS principle .
So, imagine that there are two programmers. One of them is smart, read a bunch of articles on Habré, knows the GoF catalog by heart, and Fowler- in face. The other makes everything simple. The first will be called, for example, Boris N., and the second - Markus P. Of course, the names are fictitious, and all coincidences with real people and programmers are random.
So, the project manager comes to both of them (if in your universe PM doesn’t go to the programmers himself, call it something else, for example BA or lead , this will not change the essence) and says:
“Guys, we need to make bread.”
Exactly so, it was "done" without specifying the method of production.
What will our programmers do?

Boris creates his first abstraction - the Product class, from which he inherits the Bread class, and instantiates the class of the factory method ProductFactory - createProduct ().
Marcus does about the same. It creates the Bread class and the Manager class with the factory createBread () method.
While the difference is minimal. The project manager, having a little deeper understanding (it only seems to him so, yes) in the needs of the customer, comes in a second time and says:
“We need the bread not only to be made, but baked in the oven.”
And immediately it was impossible to say that the bread is not baked in a vacuum, but in the oven? Well, what do programmers do?

Boris renames the ProductFactory class to Oven, and highlights the abstraction - AbstractOven. To be completely beautiful, he renames the createProduct () method to bakeProduct (). Thus, Boris performed refactoring for the first time, using “extraction of abstraction”, and also implemented the template “abstract factory” exactly as it is described in the literature. Well done, Boris.
But Marcus does nothing. From his point of view, everything is so good. Well, maybe it’s worth changing the createBread () implementation slightly.
The phase of the moon changes, and the manager comes to the programmers for the third time. He says:
- We need stoves of different types.
Well, right.

Boris, happily rubbing his hands, creates three heirs of AbstractOven - ElectricOven, MicrowaveOven and GasOven. And he removes the Oven class as unnecessary.
Marcus is also making changes to the program. It adds the integer parameter ovenType to the createBread method.
For the fourth time, a manager comes to the programmers. He just read one of the books in the series “I Know the World.” The interference of new information and PMBoK gave an unexpected result. The manager says:
“We need a gas stove to not be able to burn without gas.”

Boris absolutely unreasonably believes that there can be only one source of gas. And for such cases, there is always our favorite template. He creates the GasSourceSingleton alone, and to reduce connectivity, he implements it through the GasSource interface in GasOven. Hooray, he applied dependency injection through the setter!
Modest by nature, Marcus creates a real private gasLevel field in the Manager class. Naturally, you have to slightly change the logic of the createBread method, but what can you do!
But a couple of days later, the manager comes for the fifth time, and licking his lips full, says:
“We need so that the ovens can also bake pies (separately - with meat, separately - with cabbage), and cakes.”
Programmers also want to eat, so they get to work.

Boris is already starting to feel something like that, but he can’t stop. How does the stove know what exactly it needs to cook? Obviously, she needs a cook. And Boris, not long (or maybe long) thinking, creates the Cook class. He will have a cooking method that takes an abstract oven as an input - cook (owen: AbstractOwen): Product. After all, this is logical - the cook takes the oven, and with its help cooks. Then Boris creates several more descendants of the Product class - Cake and Pasty, and from Pasty inherits MeatPasty and CabbagePasty. And then for each type of product creates a separate cook - BreadCook, PastyCook and CakeCook.
It seems still normal, but it took a lot more time than Marcus, who simply added another integer parameter to the createBread method - breadType.
For the sixth time, a manager comes. By the way, what he asks for now is not a requirement of the customer, this is his own initiative. But nobody will know about it, right?
- We need to bake bread, pies and cakes according to different recipes.

“Hm,” Boris says and recalls the “builder” template (along with the “free interface” , of course). He creates the Recipe class, and to it - the builder RecipeBuilder. He injects the recipe (SUDDENLY!) Into the stove using the setRecipe setter (recipe: Recipe).
And Marcus (you won’t believe) adds another integer parameter to createBread - recipe.
The most interesting, as always, is away from computers. Namely: the manager for the first time after the start of development meets with the customer and finally understands why he needed a stove. He (the manager) comes to the programmers for the seventh time and says:
“We need bricks to be burned in the oven.”

For Boris, this is the last meeting with the manager, but nevertheless he is making changes to the architecture with all his might. It highlights the abstract class AbstractHeatingSmth - an abstract heating something. For him, he creates a HeatingFactory factory. From AbstractHeatingSmth, it inherits ProductOven and Furance. The latter has a factory makeBrick method that instantiates a Brick object. But nothing works. The reader is invited to independently find a mistake in the architecture.
Marcus, too, is not so smooth. He has to create the third (!) In a row class. He calls it Brick, and adds the makeBrick method to his Manager.
Of course, one could argue that Marcus is creating Ad and Israel inside the createBread method , and this is actually so. But using the template method template, the mess can be structured. And to understand the abundance of factories and abstractions, well, a little more complicated.
The conclusions that I want to draw are probably a little predictable.
Boris's approach is good in that almost every part of the system can be isolated and covered with tests. But it takes indecently a lot of time to create so many classes, and each change in requirements will result in a cascading code change. An attempt to make the architecture flexible, anticipating the wishes of the customer, usually fails - the architecture bends in the wrong place. After all, as you know, "the world is not just more amazing than we imagine -
it is more amazing than we can imagine." And, having received the next change request, the programmer is convinced of this like no other.
The approach of Marcus, of course, does not allow the use of unit testing, but on the other hand, it gives the result much faster, and the changes are made with less blood. This approach is the very quick start that startups of all stripes want so much. And, strangely enough, it is really easier to understand such code, because it is simpler.
And to rewrite everything anew, if that, - it will always be in time.
The picture was taken from here by
UPD. It is very pleasant that the article received so many responses. Here , for example, the solution of this problem in Haskell, but that's on the List.
UPD.2 And here is the C # version. Who else?
UPD.3 Bread of Marcus and YAGNI