(Like this article? Read more Wednesday Wisdom!)
It seems that nobody wants to solve problems anymore. Instead, I have the distinct impression that people want to build engines and platforms that some mythical “other people” could seemingly use to solve actual business problems.
Personally I blame the invention of libraries and the linkage editor.
A long time ago, when computers were made of wood and furry creatures from Alpha Centauri were still real furry creatures from Alpha Centauri, you had to do everything yourself. Want to talk to a disk? You better know how to write a channel program and the ins and outs of channel programs and the EXCP instruction. Want to calculate a sine curve? Well, here are some addition and subtraction instructions and good luck to you.
That situation could not persist of course and pretty soon people started writing libraries to help them solve problems faster. With just a little bit of work you could turn the piece of code that you had written into a generic piece of code that could be reused by yourself and, and here is where this gets dangerous, other people.
From these humble beginnings sprung forth an entire herd of people whose only job it was to write libraries of code that could potentially help you solve problems faster. This has reached the point where the applications we write are really just a few lines of code on top of millions of lines of other code that we download, compile, and then load, import or link. This practice has reached such unfortunate heights that even trivial functions are now routinely imported from the Internet, which then makes tons of software break if the author of that function is annoyed with something or someone and retracts their code.
The ability of software to generically solve a particular problem is both a blessing and a curse.
It is a blessing because it allows us to get things done quickly. A lot of software development now comes down to slamming some big ass components together with a few lines of glue code. The whole ML and data science world is a prime example of this phenomenon, with fantastic libraries and platforms for manipulating data, and a bazillion mechanisms for creating powerful ML models. Without TensorFlow, PyTorch, and Jupyter that whole field would probably still be in the dark ages.
It is however also a curse because it gave tons of software engineers the idea that instead of solving problems, they should be writing platforms and engines that can be configured to solve problems. I swear to God, if I see one more rule engine that can be configured in JSON to run if-then-else statements, I am going to explode.
Especially if the rule engine designers are blissfully unaware of core science in the field, such as the RETE algorithm.
In many cases, engine-itis comes from the fact that the actual problems to be solved are not yet known. In design reviews where I am confronted with the umpteenth workflow engine, I typically ask one simple question: "What are the actual workflows you need to run?" If the answer is that they don't yet know, I advise them (in the nicest possible way) to first figure out the workflows before they start thinking about the engine.
Simple case of getting the requirements first before thinking about the tech solution.
I understand the thinking though: We don't know the workflows yet, but if we build this generic configurable workflow engine, then whatever the product managers come up with, we can implement with our engine which is going to be amazing and flexible and configurable in JSON. Actually, it is going to be so amazing that the product managers can implement the workflows themselves, without even involving the software engineers!
I have never seen that narrative turn into a reality.
Of course the engine will not be that amazing, and it will not support every eventuality that the (as yet undefined) business problems are going to call for. Also, the product managers will never implement their own workflows without involving the tech team. Instead, what we will end up with is another half-baked, incomplete, undocumented piece of software that will never be used for anything else than the problem space that the team was originally tasked with to solve. But in the meantime the organization has incurred the extra cost of writing an interpreter for yet another (possibly Turing-complete) workflow configuration DSL represented in JSON (or worse: YAML) and yet another crappy engine for running a distributed state machine.
Another instance of generic software that gives me an instant rash is a rule engine. Most rule engines I see are a collection of boolean functions that answer business questions that have different outcomes based on attributes of their input object(s). "Is a background check compulsory for an employee hired in job X in country Y?" "Can plane model X with engine Y use unleaded avgas?" "What is the state tax threshold in state X for an immediate descendent who is N steps away from the deceased?" That sort of stuff.
The thought, though not in itself incorrect, is that this type of logic both simple and liable to change over time, as laws and/or business policies change. Because of this, there is every reason to remove that logic from the mainline of the application and store it somewhere separately so that it is easy to locate and change when that time comes.
But unfortunately people overdo it. Instead of conveniently storing that logic in actual code in a separate package or module (where the compiler can see it, your debugger can step through it, and tree shaking can remove it), they go overboard and invent a new DSL for business rules, a configuration system for completely separating the code in that DSL from the main application, and an interpreter for running the business logic rules in that DSL.
Then when push comes to shove, we now have yet another bad DSL, poorly documented, without decent tooling, that nobody is really proficient in, as well as business logic written in that DSL that cannot be changed without deep involvement from some software engineering team that is also struggling to understand what is going on because the original developer is long gone.
Too many people confuse good software engineering practices with being overly generic. If your code needs to do five things that are more or less similar, it is a great idea to abstract that into a separate module and use polymorphism, inheritance and other great practices to prevent duplicate code and ensure that improvements spread out to the entire application. That’s why we invented Object Oriented Programming. But please do not go to the next step of extracting that code into a config file that is so complicated that it is for all practical purposes a separate programming language that claims it can be used to solve all sorts of other problems too.
It can't, and it won't be.
Additionally, you might want to ask yourself if the code in these configuration files will ever be changed and released outside of the overall release pipeline of your application. If the answer is “no”, then you might as well write that code in the same programming language as the rest of your application and get the benefit of IDEs, test frameworks, compilers, linkers, type safety, and other goodness.
If you go the “configurable engine” route you will most probably just have created the world's next piece of tech debt. You will also have significantly increased the surface for potential bugs in your applications. It would be the same as developing part of your application in Javascript, stored in separate “config” files, and then pack a Javascript interpreter into your app to run that code. Which, by the way, if you really want to do it this way, wouldn’t be a terrible way to go about it because now at least you have a ready made interpreter, a language that many people know already, IDE support, and (other) useful tooling.
There really is a need in the world for good platforms and generic software components, but developing these well is extremely hard, and you are most likely not going to do it as part of developing an application to solve some business problem. You don't have the right amount of time, probably not the right amount of expertise and experience, and your company likely does not have the stomach for it.
Sometimes great (generic) pieces of software do come out of companies. Kafka comes to mind. But typically these are great companies and that software is developed by great engineers who were tasked with solving a very deep technical problem. They went forth and created the platform and then spent huge amounts of the company’s and their own time to make that system great and successful. And even then most of these systems fail.
Most platforms and engines I see proposals for should just be a library or a separate module in the application. I know it's not as cool. I know you fear it might not help you get promoted. I understand it's fun to write the code for a generic solution; much more fun in fact than writing gnarly business logic that requires a deep understanding of the actual requirements. But I also know that the vast majority of these platforms and engines will never be used beyond their original use case and more often than not become hard-to-deal with problems for future generations of software engineers.
Don't do that. Don't write sucky DSLs expressed in JSON. Don't write "generic" systems that do not have any use beyond the immediate problem that you have been asked to solve.
You ain't gonna need it. They will become problems. Don't create problems.
I could not agree more. The problem is that the software industry’s workforce has shifted from engineers to power users. The engineer is supposed to do what you suggest: provide a solution for a specific problem. What we have in the industry are power users, which do not want to be engineers anymore. You can become the configuration specialist and reject any ambition to be an engineer. The new idea is that we have either power users, or we have people inventing engines for power users.
Not everyone wants to become an engineer, not everyone succeeds in becoming an engineer, but everyone can become a biological configurator. This reflects the industry's tale: engineering skill is not necessary; it is all just a matter of setup experience. Why don't we count the hours spent on configuring systems, like pilots have flying hours? We could list the hours in our resume. Then we can also tell the tale that systems can be devised by anyone.
Engine-itis makes things look simple without being easy.
What the industry pretends to need at any cost is uniformity and engine-itis is a good response to that. Engine-itis is a social equalizer: the skilled engineer and the talented power user who recalls all config switches by heart look alike, because they struggle in similar ways with the legacy.
I think you have more than one point here, I fully agree, since ever I wish to go back to the roots: let’s be engineers and let’s engineer. Then, so many dysfunctional containers/engines with fancy DSLs (nobody needs in a properly designed system) will melt like snow in spring.
At Google, the #1 reason for writing a configuration DSL (typically protobuf) is to achieve language neutrality for the configuration surface. I would buy most of Jos's argument that all the other reasons are BS, but this one clearly isn't. Sometimes this can be sidestepped by hiding the library behind a service, but that comes with its own costs.
A library offered in 3 languages configured with a DSL (and otherwise nearly-zero-config) is a lot easier to document and maintain than a library configured natively in each of the languages.
Is it wrong for big companies to invest into multiple languages? I don't have the answer, but the article seems written with the tacit assumption that it is indeed wrong, which I doubt is clear.