If you're reading this, you probably know exactly what I am talking about. There's that moment where an engineer opens his or her mouth and spews a bunch of words that sound intelligent, cohesive, and, if you're another engineer, like total bull. They rattle on about how a problem is extremely complex and requires care, how they have delicately crafted some exotic beast capable of doing epic battle against the Demonic Foe and winning in the most gallant, obfuscated way possible.
If you're an engineer, you pull the repository, check the product, and, much to your horror, realize they have attempted to build a space shuttle. From scratch. Or hobbled together with a bunch of Russian parts never meant to be used in the same sentence.
As an adjunct professor of Computer Science at Stevens Institute and a senior engineer that has mentored many software developers, interviewed hundreds of engineering candidates, and peer reviewed countless lines of code, I am here to tell you that the best engineers do not speak in tongues and, more importantly, certainly do not code in esoteric gibberish that only they understand.
So, what's the problem? Why, too often, do we end up with complex tangles of code that appear built to survive a nuclear holocaust and, yet, usually end up crashing servers, deleting user accounts, and being rewritten by another engineer after the original creator leaves?
Simplicity translates to flexibility, extensibility, and reliability. When a system is easy to understand, intuitive, and minimalist, action and reaction are easy to observe and modify. As additional systems are added, the separation of action and reaction increases and becomes remote. That is, when something happens, the impetus and final result have a vast distance of code to be traced through and understood.
A calling B is a direct reaction with very easy to understand result. I like to say, "The distance to understanding is small."
A dispatching something of type C read by a system called D and then translated from D specific understanding to B specific understanding before being related to B by an intermediary E is... garbage.
A Land of Complexity
Every decision made during engineering has the ability to increase or decrease complexity. It is the engineer's ultimate responsibility to balance these decisions. Simplicity is harder than complexity in that it requires strong executive functioning skills (our ability to organize and use information), an understanding of how individuals use software, time to think through potential solutions to problems, and collaboration (without collaboration, you can never truly know whether the code you have written is simple for others to understand or convoluted).
The first issue is simple enough: communication. To those that do not understand engineering, the craft of code is magical. Like a sufficiently advanced alien race, we engineers can be perceived as sorcerers. More so, many of us revel in the heady intoxication of "I know something you do not" and milk those moments for all they are worth -- they allow us to exert ourselves over those around us.
Often, when teams are being formed, there are non-engineers making decisions about who will lead the engineering department or team. When inexperienced engineers or non-engineers are involved, the magical words of technology can be more than enough to allow less-than-capable individuals through the doors. Damage is most impactful toward the beginning of a product's development, when the foundations are being first hammered out; so, any damage done by the complexity monsters at this point can have catastrophic effects.
That first arguement feels iffy. It's me saying, "Oh, you're not an engineer, you don't know what these guys do!" which is all part of the problem, right? Let me give a more concrete reasoning for these complex behemoths: mentorship.
If you could take every engineer that has mentored an individual and line them up, you would garner a deep understanding of the capabilities and methodologies of that person. Sadly, this is not possible. The interview process is meant to hammer and chip away at walls until the root individual stands, naked, before you -- or that's the idea, anyway, engineers tend to get wrapped up in asking tongue twisters about sandwich making and math.
What am I saying? Well, the behaviors engrained in an engineer early in his or her career define how he or she deals with complexity. Now, that sounds fatalistic, many of you will say. Deal with it. It's the truth. Until an engineer has been mentored to understand 1) what it means to work on a TEAM 2) how to collaborate, not compete and 3) pride in craftsmanship, he or she will, genuinely, not be aware that he or she actually needs to be mindful of these elements!
The Lone Wolf
The most complex code I have witnessed has come from individuals with histories of "lone wolf" coding. They have never had formal mentors. They have assumptions about how other people use code based on how they use code -- not on experience actually collaboratively building products -- and they have been perceived their entire lives to be "aces" (as they have never had anyone actually look at the raw instructions they have been leveling at problems).
Good mentorship implies review. The point isn't to harm ego, put someone in his or her place, or embarrass them; rather, the idea is that engineers as a collective, working together, are stronger than when they are solving problems on their own.
The roman soldier could fight individually, this is true; yet, we remember them most for their exemplary collaboration on the battlefield. A horrible engineer is made better by collaboration. A great engineer is made worse by solitude.
Generalize It, Baby!
Me: "Why is this so complex?"
Them: "Well, we will want to use this across all of our products."
Me: "How many is that?"
Them: "Well, at the moment... 1"
There are a few simple concepts that have poisoned many a project. Just because we can create generalized, reusable code does not mean every piece of code we build HAS TO BE generalized and reusable.
The worst engineering I have witnessed has revolved around an engineer attempting to "think ahead" and build something to solve future problems that never materialize. In doing so, the code becomes complex. Why? Well, when you try to solve every problem in the universe, you have to cover a lot of cases! You have to build a lot of systems, of course.
"Architects" (the bad ones, that's why I used quotes, there are great architects, as well) love to do this. In an interview, I had a candidate say, "I like to build libraries for my team to use." My simple response, "Do you use them, too?" confused him. Why would he have to try his own product?
Now, the converse, not thinking ahead, is just as dangerous. I am not advocating building only purpose-built systems that can never be expanded; rather, I am saying that simplicity and clarity of purpose make systems that are better able to be extended AS NEEDED based on real, actual needs instead of prediction.
I think that's enough to think about for now. I apologize for the complexity :) I didn't have time to sit and simply compose a blog. Please leave questions, comments, etc.!
Andrew A. Grapsas is a senior software engineer specializing in gameplay and engine technology in the NYC area. You can read more at http://aagrapsas.com and follow Andrew on twitter at http://twitter.com/aagrapsas