Recognizing and Responding to Brute Force

Software results from the efforts of many people. At its most basic level, eventually some programmer expresses intention to a computer in a language or mechanism that results in the desired goal being accomplished. Programming is the term, though coding may be more specific. And the choices made by the coders have profound impacts on the results.

As a developer and technical trainer for many decades I have acquired a “feel” for good coding when I see it. And when the code isn’t good, I have often struggled to express why.

There are excellent materials (blogs, books, classes, lectures, journals, etc.) on how to code. Some even cover how to code well. Most, though, handle the objective parts (such as language syntax and expression of logic) much better than the subjective parts.

What makes code good? Or, more useful to this discussion — what makes code bad?

This has been, for me, subjective for too long. There are specific issues which are quantifiable and can be expressed, if I only had the words. Here, then, is one of those issues with the words.

A symptom of accidental complexity that I refer to as brute force.

My purpose is to address one of the aspects I’ve been referring to when I say “I know it when I see it.”

Discovering Better

“I know it when I see it” is not good enough.

Time for me to put up or shut up. What is brute force? In particular, what can be done to identify it, and then what can be done to improve it? No more hand-waving.

Well, it’s complex…

I’m going to show a bit of Python code from an existing Flask tutorial. This tutorial does a good job of showing both Flask and Swagger, starting from nothing to a simple working example. Like almost all tutorials, the code presented faces a few challenges:

  1. It has to be enough code to demonstrate the purpose it’s intended for
  2. it has to be little enough code that a reader (a human) can understand what’s in front of them without having to spend hours learning dozens of files worth of a production code base

Sample programs and code examples are not representative of what production code looks like. They can’t be. They shouldn’t be. Oddly enough, though, because of this, they almost always exhibit brute force.

I am giving this disclaimer because even when I make sample code it is using brute force and this is not a sign of a mistake on the parts of the literally thousands of tutorials and examples that I’m as grateful for as are the rest of my readers.

So, from an excellent tutorial on Flask and Swagger (from Imaginary Cloud, no relationship other than I admire the article):

@app.route('/basic_api/entities/<int:entity_id>', methods=['GET', 'PUT', 'DELETE'])
def entity(entity_id):
    if request.method == "GET":
        return {
            'id': entity_id,
            'message': 'This endpoint should return the entity {} details'.format(entity_id),
            'method': request.method
        }
    if request.method == "PUT":
        return {
            'id': entity_id,
            'message': 'This endpoint should update the entity {}'.format(entity_id),
            'method': request.method,
		'body': request.json
        }
    if request.method == "DELETE":
        return {
            'id': entity_id,
            'message': 'This endpoint should delete the entity {}'.format(entity_id),
            'method': request.method
        }

That block of code refers to a single dependency (NOTE: dependency is a magic and important word) from the ‘flask’ package.

The code is fine. It works, the result of the computation is as expected. The indentation is proper, there is no problem with the choices of names. As an example, it’s small enough to be understood for this aspect of a REST API.

Brute Force Detected

Now, imagine that was NOT an example. If that were production code my brute force alarm is signaling me.

Now, before I explain why, I’m finally going to define Brute Force:

Brute force is present in a unit of code when understanding a single intention for the code requires more than what is present because of multiple dependencies or multiple intentions.

The problem with this example code is that the single function has multiple intentions. In this case, the intentions are “support for get,”, “support for put,” and “support for delete.”

The only non-local dependency is the decorator from Flask. A single dependency can not be brute force, because it’s not beneficial to have no dependencies — assuming it’s even possible.

Routinely in production code there are “dispatchers” or “managers” (classes or functions depending on the language and tooling) that aggregate decision making that spans multiple intentions. These types of constructs are (per the definition above) perfect examples of brute force.

Assuming my definition is accepted, and this is an example of brute force, what makes this problematic?

Why is Brute Force Bad?

At this time, programmers remain human. Perhaps, in the future, we’ll meet aliens who will have different mental faculties. Currently though my audience is expressly made up of human programmers. This is important and I call it out precisely because nothing I say about the problems of brute force applies to code that is not read or written by humans.

In particular, the output of code generators and compilers would often be brutally repetitive and mix tons of dependencies and intentions. That’s perfectly fine. We don’t care that the machine code generated by the compiler consists of huge blocks of wicked code.

For us humans the ability to understand anything is limited by our mental capacity of roughly 7 plus or minus 2 discrete chunks. Every “section” of code that has a different intention within the same unit of work, every external dependency that requires understanding of something not visible in a section and any piece of logic that requires constraints which are divided (such as layers of nested if/else) increases the count of discrete chunks the human needs to work with.

This limitation, the maximum count of discrete chunks, is as real as our inability to breathe vacuum or eat rocks. No amount of experience, no clever wall posters, and no wishful thinking is going to change this aspect of how our brains work.

Brute force coding does not harm the result of computation nor does it confuse the computer. But it dramatically increases the difficulty for the humans to read and understand it later. This is why brute force is bad…it’s easy to write it by just “vomiting out” (or copy/pasting) specific steps as needed into a function or script but the result isn’t easy to test or maintain.

The complexity isn’t because of the problem being solved, so it’s accidental complexity.

Accidental Complexity

Fred Brooks in his No Silver Bullet essay described the concept of “accidental complexity” as problems that engineers create in their own work and can therefore fix. Every single time we struggle because of the code itself, other than because of the challenges of the goal the code is meant to address, this is a personal failing of losing out to accidental complexity.

Brute force is one (of many) types of accidental complexity. Because of this, it is one we can absolutely fix when we encounter it. And we should.

Dependencies Revisited

The concept of brute force is true for many intentions, but it’s often most painful when it involves many dependencies.

Imagine a more realistic Flask hosted Resource implementation based endpoint in the microservice container.

I don’t show one because it would be large, with many imports and many calls. Seek any sample Flask code that uses encryption, relational databases, etc. That’s why I’m dropping to pseudo-code for this.

So, a function where the input comes in with parameters to perform a query. The following steps are then coded inside the single function:

  1. The parameters are extracted from the posted data and any query parameters (?id=1 sort of parameters) which uses a dependency on Flask and possibly JSON
  2. The code fetches a database connection (a dependency on the DB API)
  3. The code queries the database (dependency on DB)
  4. The code manifests entity objects (dependency on resource/entity classes)
  5. The code performs some computations (intention actions as from above!)
  6. The code bundles the results into a deliverable format (dependency on JSON)
  7. The code delivers the result by calls to the Flask response (dependency on Flask)

In that simple function there are dependencies on:

  1. Flask
  2. DB/Persistence
  3. JSON
  4. local entity/domain library

The more powerful the microservice, the more likely that there will be additional dependencies. I have seen things like:

  1. crypto signature validation for each call
  2. logging
  3. metrics capture

The effect of writing production code by having manager or dispatcher functions doing work rapidly escalates into a huge amount of logic buried in code that should be focused on integrating the web API to the domain solution.

Then It Gets Worse

The problem escalates when multiple versions have to be supported during transitions, or when multiple protocols are needed. For instance, requiring that an accounting facility be available via API or via Kafka queuing means that none of the logic should be stored in the API endpoint OR kafka message handler. However, sloppy developers will copy the logic to the Kafka message dispatcher if it already exists in the API handler.

One of the common symptoms of brute force in code is the recognition of repetition. Instead of using any of the techniques to reduce brute force, it’s “easiest” to just copy/paste/edit. Sometimes, the right parts are edited. Sometimes, it just leads to subtle bugs that are hard to find and often hard to fix.

In order to have confidence in any of the code, it must be tested. Attempting to test brute force code immediately means the need to manifest everything it depends on.

Testing Nightmares

So, we want to test code in a microservice. Of course, we need to get our code running. We’ll need to launch a docker for the DB, so we don’t interfere with anyone else. We might need to launch another docker for Kafka and another few microservices too … we better be able to run a whole environment to test any code.

The setup for unit testing then becomes complex. We can’t pop in and invoke a test entry, we have to run an environment. Of course, we need this for promotions from dev to integration, or to production, but I’m writing code and need to test a few lines. It’s in the middle of all this … this stuff.

Of course, any programmer can learn to do these things. All members of the team who write code need to know all the dependencies, if just to test. This is normally DevOps, an entire specialty, but, well, all coders need to know that too if their code base uses brute force.

Staffing Problems

That leaves us with staffing problems. Many job postings require N years (sometimes eight, sometimes five, depends on the job request) of experience for each of the libraries, technologies, and methodologies used in any of the system.

Instead of having a team where there are people who delve into limited areas, the team requires everyone be interchangeable because no matter where the code anyone writes goes it will need to draw in the rest of the environment to execute it.

Every one of the libraries, frameworks, and technologies that are used to make the system easier to work with generally do work. Flask is a lot easier than writing raw BSD socket code and parsing the HTTP from a stream of octets by hand. It’s much easier to use a JSON library than do string processing of the JSON string. Basically, the dependencies exist because they provide a value.

Developers have no problem using any of the libraries and tools. While rarely you need a deep understanding of some particular aspect when troubleshooting, most of the time, the tooling works with only a few hours spent “coming up to speed” with it. Go to the sites for the tools (Flask, Spring Boot, Hibernate, etc.) and they inevitably have tutorials that show how to do the important things and explain why.

However, a developer who’s been using Flask would be rejected for a Django project. Even though the majority of the Python coding between the two projects is just Python (except where brute force mixes things badly) the lack of Django in the resume is often enough to cause rejection.

In this way, modern recruiting practices with “machine based matching” of resumes against project requirements fails to find available talent when the underlying code suffers from brute force. Yes, the raw coding skills manifest this high up the chain into project management.

How To Fight Brute Force

So, to get code that is easier to understand, to maintain, to test — and to improve the team with more easily hired staff — what are some techniques to reduce brute force?

The important first step is to realize it’s worth searching for and eliminating brute force. Then there are a variety of very simple things to help once identified.

What’s strange, these are the basic things that are always talked about as virtuous. But, without the recognition of brute force it’s too easy for someone in a rush to say “Yeah, I know I should do X, but it’s not a big deal. This isn’t school, it’s the real world.”

What are some specific things?

  1. Decomposition into functions and methods
    This is probably the oldest mechanism for reducing complexity (even to the point of helping reduce McCabe’s cyclomatic complexity).
  2. Provide a separation of interface and implementation
    This is the basis for most of the OO claims. Some of those claims are true (though OO itself can often cause an increase in dependencies unless it is very carefully managed).
    Java’s explicit ‘interface’ vs ‘class’ is a language facility that enforces separation and unsurprisingly most excellent Java is “written to the interface” as a result
  3. Externalize the intentions
    This gets into many of the advanced techniques such as domain specific languages. For example, using SQL instead of opening files, looping through them, and doing logic. SQL is one of the most successful of the DSLs.

Just paying attention to brute force and refusing to tolerate it leads to programmers putting more discipline and attention to each block of code they write.

The funny bit … it takes only a bit longer to write code that isn’t brute force. It takes far less time to test, less time to debug, and less time to maintain. Just the difference in time for testing I’ve found generally exceeds the “extra time” to write the code better.

The actual foundation for improving code in general is abstraction. I’m not covering it in this article because I want to focus on when abstraction needs to be done. Otherwise, it sounds like abstraction is just an academic term that is nice but not always practical. This is absolutely untrue. It’s just hard to make large enough examples that are done badly to enable a refactoring that helps that doesn’t look contrived.

Remember: example code is generally brute force. Abstracted code by its nature doesn’t show a flow from start to finish in a single block. That makes for hard to understand examples.

We’re not writing sample code for production. Our code requires a team of people each of whom can test their work rapidly to make code that other team members can review and maintain. If the actual production source code is brute force, we’re not going to have the quality of results we need and our stakeholders deserve.

Now, you too can know brute force when you see it. May your teeth then grind in horror — it’s the first step to fixing it.

Keep the Light!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s