How to Be a Better Software Engineer:

Scott Davidson
4 min readFeb 3, 2021

Reflections on a decade of programming

I have been reflecting on my last decade of software engineering. There are many lessons and skills I have learned along the way. A decent portion of our responsibilities as software engineers is to write code for solutions we designed. I like to come up with succinct words of wisdom that summarize what I have learned like “think twice, code once”, “every line of code you write is a line you have to maintain”. Here are two tactics to generally follow that encapsulate a whole mindset for being a better engineer: write pure functions, choose good names.

Pure Functions

A pure function in the strict definition meets the following two properties:

  • The return value has no variation for the same arguments
  • The function evaluation causes no side effects e.g. like changing a global variable or an argument.

There is lots of literature for describing pure functions, so instead, let’s focus on why they fall in my top two most important tactics for better code.

Let’s look at what the first property brings to the table. When the return value is the same for the same arguments, code becomes a bit more predictable and less complex. Code becomes much easier to test because you just have the inputs to the function and the expected output. Many of the needs to create a mock for a service are removed too.

In Lean Software Development, Mary & Tom Poppendieck state that “the best way to maintain institutional knowledge about a system and keep it maintainable is to deliver a suite of automated tests along with the code.” There is a domino effect when striving to write pure functions. Pure functions -> easier code to test -> tested code -> documented knowledge and maintainable code. You still have to write tests, that doesn’t come for free, but hurdles are removed.

I like what Robert C. Martin had to say about side effects, “Side effects are lies … They are devious and damaging mistruths that often result in strange temporal coupling and order dependencies.”

When we reduce and remove side effects from our functions, code becomes more declarative and less stateful. Mutations of non-local variables are a side effect, so we begin to treat variables as immutable and reduce the global variables. The code becomes less coupled and engineers are able understand the behavior of the code more quickly.

It would be tough for a non-trivial application to not have some side effects. As you strive to write pure functions, keep the side effects compartmentalized and as minimal as possible. You will end up with a thin shell of side effects around a core of solid predictable and tested code.

In general, pure functions help the code be more deterministic and less like “spaghetti” Remember, with pure functions, the same basic principles still apply: keep the functions short and single purpose.

Naming

Yes, many people preach about having good names: rightly so. As software engineers, we spend a lot of time naming things. We name variables, functions, files, projects, on and on. The names convey meaning and understanding to not just engineers but everyone in the organization.

In a production project my team inherited to maintain, there was literally a function named “doSomething”; in a somewhat related portion of the code base there was another function named “doSomethingForRealz”. Obviously, the functions were doing something, but the names conveyed no information into the system. This made it difficult and more time-consuming as the new owners to understand this system.

A colleague spoke of a former company that had “fun” code names for all the different services. This became a whole additional layer of complexity and translation employees had to learn and adopt.

Robert C. Martin has a good list of rules for naming in Clean Code, but you need to know the solution space and the problem space to be able to choose good words/terms to apply those rules to.

In Domain-Driven Design, a key practice is to develop a ubiquitous language for the bounded context (problem space). This language is used by everyone: management, product staff, and engineers. This facilitates communication between departments for better understanding and alignment in direction.

There have been times where I have been guilty of not caring enough about the problem space to learn it, but when engineers do, good things happen. Matt Barker, head architect at Pluralsight, says it well in a LinkedIn post: “Code is an expression of domain understanding. It’s your business strategy for the problem set at hand, expressed in syntax a computer can read so the computer can execute your strategy faster than any human can. So, the more your engineers … understand the problem domain, the better your code will be.”

When engineers strive to choose good names, they have better communication, better solutions, and better code.

Final Thoughts

At the core, pure functions help to reduce complexity in our code, and well-named objects help to increase the understanding and purpose of our code. Yet, using these two tactics set up a mindset that has positive ripples throughout an engineer’s responsibilities and increases their value. Remember some tools are better suited for particular solutions than others, but if you take the concept of these, they can be adapted to just about any area.

--

--