What are the least intuitive fundamentals and best practices in software development?

ben profile image Ben Halpern ・1 min read

Some things we do kind of make sense in and of themself. Some things have evolved to be the way we do it but just don’t map up to our brains cleanly at first.

What are the non-intuitive parts of our craft?


markdown guide

Probably the idea that adding more developers to a project slows it down.

We've all heard the quote about how nine women can't make a baby in one month, but it can be hard to understand why it's applicable to developers too.


This has to be tempered with how many you are adding. If you have a small team, say 3-5 programmers, adding another programmer should definitely speed up development.

If this is not true, then it's a reflection of your code, processed, and organization. If you are in this, then try bringing somebody on with the key purpose of fixing this state. It'll bring rewards quite quickly.


Certainly it's not an absolute.

If you have a project with clean code, good test coverage and continuous integration, then bringing someone else on board can be pretty painless.

Under other circumstances, it can be downright excruciating. I'm working on a Zend 1 legacy code base with some downright awful code that I inherited, as well as horrible jQuery spaghetti code, and bringing someone else on in the project to do some of the front end work did mean I had to take some time out to bring them up to speed on various aspects of how it worked, which in turn slowed me down.

I think that would probably trigger my problematic code consideration. I'm not saying it won't slow you down on some projects, only that clean projects should benefit.

Though, even here, how long did it slow you down for. Do you think the junior won't contribute more overall than the time you invested?

In this case, it wasn't too bad because:

  • The new functionality was a new home page, so I'd built it in React as opposed to the PHP and spaghetti jQuery of the rest of the site
  • The developer brought onto the project is experienced. She hadn't used React, but had used Vue, and could easily draw parallels between the two
  • The division of labour. My CSS skills have never been that robust and I'm happy to stick to writing server-side code and Javascript, so I just put together some basic React components for the front end and left it to her to style them.

In this case, most of the loss of velocity came from things like the adjustment to React and explaining the downright awful application structure. It was definitely worth bringing in someone else - it'd take me a lot more time and hassle to do a much crappier job of the front end.

However, if it had been a case of bringing in another back-end developer to work on similar tasks to me, I'd have had to spend a lot of time explaining what I could, going through the awful parts of the code base, and avoiding stepping on each other's toes.


This 1000%.

I'll compound it with: hiring a junior will never speed up development. Hire juniors when you have the time and breathing room to train them.


I disagree with this. Unless your hiring practice is broken, or your code is in awful shape, hiring juniors should add immediate benefit to a team. They may not be as productive as existing team members, but they should definitely improve total output.

You don't need to devote lots of time to mentor newcomers. I've never really had it interfere with my own work.


I think juniors are often better off working on their own projects if possible, rather than integrating into a larger project. Ideally these should be small and comparatively simple, and they can work their way up to larger projects. Code reviews would allow regular feedback on code quality and allow for potentially problematic code to be identified and refactored away.

I'd only feel comfortable bringing a junior onto a large existing project in cases where I already had good test coverage and continuous integration.


Pretty obvious actually. The number of communications scales quadratically so then you need to put them into teams to stop them talking to each other at which point you've got multiple projects so then you need to manage the interface which means people have to talk to each other...


High Cohesion And Low Coupling

Having an understanding of what building systems or code with high cohesion and low coupling really means.

Having code with high cohesion will inevitably "look" like you are repeating code that could be generalized.

Let's say we have two (or more) classes that have very similar code in them. Most people will invoke DRY and attempt to generalize anything that looks the same.

But, once you create an abstraction to generalize that code you've just created a new shared point of coupling. Over-time, this practice creates code with high-coupling and low cohesion since everything is tied to each other via transitive dependencies.

I've seen SO many projects suffer from an irreversible web of dependencies because everything was generalized. Then, new requirements come - and the easiest thing is to just add a new layer of abstraction on top of everything else! Because, after all, creating more abstractions is a "good" thing 😉

It needs to be considered that different classes, modules or systems do not have the same responsibilities - and therefore will evolve differently over time. Any shared dependencies (libraries, classes, functions, etc.) will make this harder. Especially in the case of sharing many - massive problems will arise.

The biggest selling point of micro-services (which everyone praises) is that it promotes systems with high cohesion and low coupling.

Almost 100% of the experts in that area will stand unmoved on the idea that services should never share any code (libraries, etc.) and never share data stores.

Basically, the exact opposite of what most of us end up building!

I've seen shared code become a detriment rather than a blessing more-than-not.

Dependency Direction / Flow

To add another... on a similar note, a not-so-obvious principle is that the order and direction of your dependencies matter.

For example, don't mix web related libraries into your core business rule modules (package, project, whatever your language calls it 😋)

If, let's say, you have your main business rules and logic in some core library. And, if that is referencing some web or MVC related library, depending on the language, you'll probably have issues when you want to then expose that core business library to a new API project you are building (def an issue if using .NET!).

There would be (complicated) ways around that. But if the dependency wasn't there in the first place then you could expose your main business rules to both an MVC project and an API project with much less of a headache 🤣.


I’ve gotta go with regex. I didn’t ever really get “taught” regex, and Googling to find that the answer to my problem was a bunch of gobbledygook was a trip.


The funny thing is, I've found it's sometimes far easier and cleaner to just write a full parser than a really complicated regex. A pretty decent recursive descent parser only takes a few dozen lines of code, and you get full recursive structures and an AST, which are a whole lot easier to work with than complex capturing groups.

Regex is a very useful tool, but don't let that be the only tool in your arsenal for String processing.


I've been cheating for years using: regex101.com/

Best thing is it explains what each bit is doing so I can learn as I go along.


I agree. I avoided RegEx's for a couple years. One morning I sat down with a cup of coffee and forced myself to thoroughly read through this chapter (great book in general). Now I'm using RegEx on a daily basis and loving it. I really liked the diagrams in the Eloquent chapter, helped me make sense of the gobbledygook.

I mostly write JavaScript, but that chapter also helped me a lot with writing Nginx/Apache proxies and rewrites. Now I'm not doing Google-guess-and-check when writing proxies in Nginx 😆.


I'm going to guess that you have rubular.com bookmarked.


I just wrote a node-based regex editor:

GitHub logo johannesvollmer / regex-nodes

Visualize and edit regular expressions for use in javascript

Regex Nodes

This node-based regular expression editor helps you understand and edit regular expressions for use in your Javascript code.

If your regular expressions are complex enough to give this editor relevance you probably should consider not using regular expressions, haha.

Why Nodes?

One of the problems with regular expressions is that they get quite messy very quickly. Operator precedence is not always obvious and can be misleading Nodes, on the other hand, are a visual hierarchy. A text-based regex cannot simply be broken into several lines or indented because that would alter the meaning of the expression.

The other major benefit of nodes is that the editor will prevent you from producing invalid expressions. Other regex editors analyze the possibly incorrect regular expression that the user has come up with. The node editor will allow you to enter your intention and generate a correct regular expression.

In addition, nodes…

If you have some spare time, let me know what you think of it!


Agreed, especially since very few examples break them up into manageable bites and instead present you with strings for Cthulhu


I learned regexes by learning Perl in 2010. Ever since then my once-fearsome regex skills have atrophied to the point that I now struggle to write one.


Just because users are asking for a feature, the reason they give might not be the root problem.

However, users are the greatest sources of finding pain points and bottlenecks throughout your program. This is commonly called usability testing.


And this is where a great BA is vital. Give the user what they need, not what they ask for. The analogy I use is going into a hardware store and asking for a 6mm (1/4 inch) drill bit. You don't want a 6mm bit, you want a 6mm hole. If I dig a bit further, what you are actually going is trying to hang a picture. If I can build you a picture hanging process that is repeatable, automated and 10x faster, now I have solved the business problem rather than letting the inexperienced user do the requirements analysis


I'm sorry if I'm harping too much on this, but it's been on my mind because it's something that I want to change in my own way of thinking as well.

Organize code by domain, not by architecture component.

Very often I find myself creating directories named "components", "reducers", "routes", ... at the top level of a React/redux app, or "models", "views", "controllers" in an MVC app. Avoid that!.

Try grouping things by what functionality they offer in the app. For example, put the User model, profile view, password reset form and logic, etc... all in a "users" folder. This way, changes are more often confined to a single directory.

I think this could actually be an intuitive idea, but we're often taught a framework or technology, starting from the architectural overview, so that's how we often structure these things in our mind when starting out. Some scaffolding tools even default to building a structure like this.


I actually find it's best to use a combination of these. Start by grouping by domain (which may be nested), and then within your domain folders have an opinionated directory structure organized by architecture component. So, for any given domain, the code is organized the same internally, but it's nicely separated from other domains and easy to navigate to.

Bonus points if you use some kind of multiproject structure where each domain's isolation is actually enforced by compilation boundaries, but able to communicate through a shared module's interfaces.


I would tend to disagree when it comes to components, I think that style of code organization leads to a high amount of repetition, and can make sharing components unintuitive.

For example, you need a simple card component on your user profile so now Card.js lives under /User, but then after some time the same card is to be re-used by another view - now in a large codebase with different devs working on the features in parallel because there is no common place where components exist, it's not totally unlikely that the same UI component is made twice, which doubles future work when the styling needs to be changed.

Even if that worst case scenario doesn't come to pass, in your code you'll end up with import Card from "../../User/Card" or something of that sort, which feels really messy and a bit discombobulating for the dev who's working in that area currently ("Why are we importing a UserCard in our Music view?") or if you decide to lift that component when it gets re-used then you'll end up with a UI component at the top level alongside your journey or feature directories, which feels equally as messy.

Routes and models and things like that are well grouped by domain feature that they represent I guess, but components should probably by and large be feature agnostic, and aren"t a good fit for this organisation technique.


I think it’s perfectly fine to have directories for cross cutting concerns like generic interface elements, logging, network communication, etc.


Non-intuitive: There's generally no correlation between amount of code and usefulness to a user. There is however a definite correlation between amount of code and amount of defects.

This leads to the best practice of keeping code as small as possible, primarily through reduced redundancy.

Non-intuitive: Comments are the least effective way to describe what code is doing. They end up diverging from the code over time, and may miss nuances. Comments can actually make code harder to read.

Code is the best way to describe code. Using effective names and clear structures is far better than comments.


Comments should never be used to describe what code is doing. It should describe why it's being done the way it is. You've got the code right in front of you, but if it isn't immediately obvious what it's doing, a comment explaining the why is quite nice. Note that these comments rarely go at the declaration site of a method, they're more effective when they're written inline, as close to its relevant code as possible.

And on the other extreme, don't go so deep into the why comments that the code becomes difficult to read. The code is still the shining star, make sure it is still easy to find the code amongst your comments. Anything longer should go in the official documentation.


Regarding your first point, I'm reminded of Jack Diederich's talk "Stop Writing Classes" where he says: we don't ship code, we ship features.

He also says "I hate code and I want as little of it as possible in our product" :)


I'm sure he'd appreciate that my book What is Programming? starts out by looking at users and understanding features, not even getting to code until about half way through. :)

Yes, we have to learn that code is a tool, it is not the product.


Reminds me of a quote (reformatted):

Goal of programmers is to ship, on time, on budget. It’s not “to produce code.”

IMO most modern C++ proponents:
1) overassign importance to source code, over
2) compile times, debugability, cognitive load for new concepts and extra complexity, project needs, et cetera

2 is what matters.

~ Christer Ericson


Isn't the importance of the code what helps ensure all that stuff under 2?


Something that didn’t make sense to me in the beginning was the whole concept of environment variables. I really didn’t understand what they’re purpose was, or how I was supposed to use them. And because of that I didn’t understand why I couldn’t just store credentials in my code.

It seems so obvious now, but for the longest time I just couldn’t wrap my head around them.


Adding more people to a late project will make it later. "If one woman can make a baby in nine months, then nine men can make a baby in one month." If you want a project to get done earlier, then start sooner.

Taking the time to keeping the code clean makes development faster. As soon as keeping the code clean is set aside (ostensibly to go faster), the team will go slower. (q.v. Clean Code, by Robert Martin.)

Software development is better suited to an empirical approach, rather than a defined approach. As such, empowered self-organizing teams are more productive than command-and-control management and assembly line style organization.

Clever code is rarely good code. Code should be written with the thought-in-mind that code is read far more often than it is written, and should be written for the other programmers, not for the compiler. (Yes, yes, yes, once in a while some critical portion of code really does need to be written quite cleverly, and will be very dense and hard to comprehend. Comment heavily. That should be rare, not the norm.)

For most software developers, the majority of software development is interacting with other people. "How can you identify an extroverted software developer? An introverted software developer will look at his shoes. An extroverted software developer will look at your shoes." The soft skills: being nice to people, being considerate and polite, being friendly, able to contribute to a meeting, able to run a meeting, being able to listen (think twice; speak once), when presenting being very prepared so as not to waste other people's time... are all very important. Not just with other developers, but with management and customers and other departments that you will interact.

A majority of software development in the world is for line-of-business enterprise applications. Sure, starting out many of us were all excited to make the Next Great Game that will take the world by storm. But the reality is a lot of software development goes to less exciting things like business, science, military, government, and consulting. Some goes to high profile shrinkwrap software (well, these days release-to-web). Only a little bit goes to games and entertainment (and ironically, those subfields have a poor track record for software developer compensation).

As a cottage industry, some software developers have made successful one-dev businesses. But even if successful, the CEO and Chief Bottle Washer has to run a business... pay the bills, pay taxes, marketing, maybe advertising. When Subset Games set out to make a game, "FTL: Faster Than Light", they were surprised that in order to make a game, they first had to make a business. Making a business turned out to be hard. (I don't have the link handy where they were interviewed, but it was quite interesting.)


Testing makes development move faster, not slower.


Oh 1000%... My most frustrating moments at job were being forced to skip tests in favour of shipping on deadline, which ultimately led to bugs and delayed shipping...


Least intuitive: NOT making decisions until you have to, thereby avoiding building complex cathedrals of software that don't quite do what the team eventually need, but carry lots of debt that deters the team from refactoring it, instead it gets layered upon.. My OCD always kicks in and I find myself creating frameworks that "you ain't gonna need"...

Best practice: I'm with a number of folks, in NOT writing code until you have solved the problem, or at least have some ideas to try out and measure what matters. We don't "churn out code", we solve problems as obviously and elegantly as we can :)


The usage of style guides those are the one that doesn't wrap around my head when I first started.

Cause I have no idea why you should do that except cases in developing. Luckily we have linters that comes with almost all of the programming or scripting languages in IDEs or text editors that I had been working with.


Definitely the idea that two, three or more people working together is less effective than everyone working in solo mode. i.e. pair- or mob-programming can be very effective.
Typing code is the less interesting part of the craft, more interesting is to have a common understanding of the problem, the way we think about these problems and how we can share that knowledge across a group of people. Two or more people working together is efficient because no hand-offs and quick syncing, less possibility to get lost or waste time trying to figure out a problem.


The worst teams are often top heavy with rock star developers. The best teams are those with a broad range of experience, culture, and ideas.


The whole concept of application "state". It's very much a part of the language of React and other functional-style frameworks/libraries, but that doesn't mean those are the only things that deal with state.

It can be especially difficult to understand state and its impact in a world like Android, where there isn't an established pattern for state management. Android has built-in support for marshaling state immutably between UI components without them needing to share a global object, but it is very tedious and requires an incredible amount of boilerplate, even when using code-gen tools. So most codebases don't do this properly, and subtle bugs start to become major issues as more and more of your app is sharing a data source with no governance.

But once you get used to the idea of passing all data around as immutable objects and not allowing yourself to "set and forget" variables, your codebase will become much easier to read and maintain, especially for new developers who didn't originally write it. Everything they need is handed to them, they don't have to go hunting for it.


Patterns everywhere. And unit testing everything. Unit testing is adding more code to check is code workig. It ussually ends in tests being green yet application not working correctly. Basically you spend more time writting tests that will be obsolete in some time as the test are usually written to test feature in a wrong way. It doesn't mean don't test. It means unit testing everything won't help you all the time.


Not easy on brownfield, but this is what TDD is for. The spec becomes the tests and you then write code that passes the tests. If business needs change, then tests change first then the code. Writing comprehensive tests is cheaper than rewriting code because the requirements were not well understood.


It usually ends up being other way around. Tests pass because people still don't understand requirements and no one knows what's the issue, than you change tests and make code pass test but in live environment it fails to deliver. Besides as from Uncle B Martin TDD is about architecture not having well tested code. You can find him telling that on multiple occasions but find Jim Complain and him discussing it. For him it's for helping you check what needs to be built how. Last time I did it in C# I ended up exposing too much services and mappers which made it confusing to deliver new features and code was to chopped up. Ended up removing almost all unit tests leaving only ones for calculations and having only sort of end to end test with scenario like approach close to regression test which made much more sense and made architectrue much more clean.

I met too many people disagreeing with usefulness of having a lot of test coverage. Having great QA team that understand clients need beats test for now in my experience.

Still my experience and people I met. I don't think no one benefits from it, just haven't met those people in person.


Software principles, design patterns, and practices should never be considered as a law that one must follow at all times. These are just heuristics. They are guidelines that we should apply carefully and only in the right amounts. For example, if we take the DRY principle to the extreme, we are more likely to introduce too many abstractions and many times the wrong abstraction is worst than code duplication. In my experience, the same can be said about almost every other software principle. So my recommendation is never to follow principles blindly and to use them with moderation and judgment. I call this idea the The Yin and Yang principle of software architecture.


Abstraction and DSLs practically.

Process more non-practically.

Everyone has opinions on what a good product development process is, but because people vary so much it's hard to say with any certainty that "[insert your favorite thing here] process is best." So many things impact process, from the very people themselves to the business model of the company. It's very hard to determine a good process, experiment, iterate, and improve upon it because it requires time, planning, and a significant amount of thinking. It's even worse if the team you work with is large but the process isn't working. Stakeholders who are unaware of the product life cycle might just see it as a broken machine that needs "motivation" rather than optimization of some kind.

I graduated from a boot camp a few years ago, and this is definitely one of the things they don't tell you about ;).


A very wide-ranging and important feature or fix may only be one line long but make or break the whole thing.


To me, it's still TDD.