Developers follow a relatively predictable development curve in their early careers. While there will be individual differences, pretty much every developer must pass through the following three stages: The Meta programming stage, the Design pattern stage, and the Functional composition stage. If left unchecked, the code produced during each of these stages may sometimes be quite harmful to a codebase. However, it represents an increase in the developer’s understanding of programming and should be harnessed and explained by more senior peers who know where that particular avenue leads.
The Meta Programming Stage
This stage occurs after two years in the industry. It’s quite amusing; it’s really always two years. At this point the developer has mastered basic control structures and data types and gains an insight which goes like this:
I don’t need all these classes, modules, and boilerplate code! I will express everything my system needs to do in a single data structure and process it using a beautiful tight loop.
Right. If you’re kind, you might say that the learning developer is making a stab at implementing a DSL.
How does this play out then? A month later, there is indeed a big fat data structure made up of arrays of hashes of hashes of arrays of hashes. It’s completely unreadable. The beautiful tight loop that would evaluate this and make the magic happen is riddled with conditionals to handle all special cases that the fat data structure can’t capture. Testability? Only if you understand how to set up the data structure, and then you face the problem of unreadable tests. Fast forward six months and even the developer who once created the thing won’t understand the intricacies of his creation.
How to mentor
It’s easy to make fun of this stage, but what’s a constructive way of handling it? First, it must be celebrated, because the developer has leveled and now truly understands how control flow statements and data interact in a program in a non-trivial way. Praise and encourage! Then throw the thing away! Senior developers must put a stop to constructs created by meta programming, because they hurt maintainability and in part testability.
The Design Pattern Stage
Have you seen the movie “A Beautiful Mind” starring Russell Crowe as John Nash, the scenes where he looks at sheets of digits and newspaper pages? In these scenes Nash’s brilliant mind makes digits and words that contain Russian code pop out. I imagine that’s how developers in the Design pattern stage—a stage prevalent after three years in the industry—feel. They see design patterns everywhere, while their peers seem totally oblivious to them; they don’t see “Singleton”, “Facade”, and “Factory” pop out in blue in the code. Consequently, they try to apply these patterns wherever they can.
Of the three stages, this is the cute and harmless one. Developers in this stage try to shoehorn every design pattern they’ve just learned into the code. The worst that can come out of this is overuse of patterns. While it’s a little annoying and requires refactoring away from some patterns, it provides fantastic opportunities for mentoring and architectural discussion in the team.
How to mentor
As a senior developer you’ll see a lot of wishful thinking and a lot of nails (as in “if all you have is a hammer, then every problem looks like a nail”). When you see a pattern applied in an incorrect way, explain it and talk about it. When you see patterns that have been shoehorned in, describe the pitfalls of naming. If it looks like a duck and quacks like a duck, it isn’t a swan. Pattern names encode very specific relations between program elements and developers expect them to be there. It’s very confusing and hurtful to maintainability if names lie.
The Functional Composition Stage
This stage comes last, sometime between 4-6 years of experience. By now developers know how to couple code to data and the basics of object-oriented development, so they get bored and explore something new, like functional programming. At their peak, developers in this stage go bananas with lambdas, closures, map/reduce, and other functional constructs. It’s not uncommon to see sequences of 6-7 foreach, map, filter, sort, orElseThrow chained together. Traditional for-loops are despised and replaced by various foreach or map/reduce constructs even at the cost of readability.
How to mentor
There is a time and place for everything. There is a certain beauty and mathematical strictness about functional, declarative constructs. When trying to harness them, there are some parameters worth taking into consideration:
- The overall shape of the codebase. If the code is mostly made up of well-written object-oriented code with traditional structured control flows, throwing in random complicated blocks of functional code may not necessarily do it any good, unless the intention is to remodel the codebase.
- The competence of the team. Not everybody is familiar with functional constructs, and certainly not the complicated ones. Is the intention to educate or to appear smart?
- Domains and testability. Chained functional code can seemingly hide the domains that are being manipulated. A one-liner may, in fact, require 10-20 unit tests because of its succinctness.
So, if everybody on the team is comfortable with functional programming, why not just change language? Some services would really shine if implemented in Haskell or Clojure in a world of mutable state OO code.
Wrapping It Up and Bonus Exercise
There are more stages, but these are the ones I’ve seen over and over again. Do you know what stage you’re at? Comment, question, or criticize (“how dare you write that the Meta
programming stage always occurs after exactly two years”)
The image for this blog post, repeated here for convenience, contains some design pattern names. Do you want to feel like the developer in the Design pattern stage? If so, try finding: builder, singleton, adapter, bridge, decorator, facade, flyweight, command, iterator, observer, specification, strategy, visitor in the image 🙂
3 responses on “The Three Stages of a Maturing Developer”
This was hilarious.
I’ll give you two bonus stages from my experience.
Overdesigning based on your own assumptions of the future or in anticipation of everything you have heard might be added later. This stage can materialize in the code through a very liberal use of abstractions that offer no immediate value beyond making it harder to read the code because of the extra levels of indirection.
A close cousin of this stage is “programing with a helmet” where the developer writes code that over-prepares for possible ways things might go wrong. This can materialize in the form of highly elaborate contingencies or convoluted ways of dealing with exceptions in ways that almost become full features – instead of just being comfortable with allowing the exceptional be just … execeptional and let it fail, then dealing with failure in a much more application holistic, simple and elegant ways.
Great article, made me smile. Thanks Alexander!
Thanks Mathias! I can only agree. However, I can’t put speculative design on the timeline with enough precision. For instance, I know that the meta-programming stage happens after exactly two years. I’ll keep this in mind for the sequel though.
Thanks for an informative post. If I knew these stages before I would have become a good developer by now.