The programmer as decision maker by Mark Seemann
As a programmer, your job is to make technical decisions. Make some more.
When I speak at conferences, people often come and talk to me. (I welcome that, BTW.) Among all the conversations I've had over the years, there's a pattern to some of them. The attendee will start by telling me how inspired (s)he is by the talk I just gave, or something I've written. That's gratifying, and a good way to start a conversation, but is often followed up like this:
Attendee: "I just wish that we could do something like that in our organisation..."
Let's just say that here we're talking about test-driven development, or perhaps just unit testing. Nothing too controversial. I'd typically respond,
Me: "Why can't you?"
Attendee: "Our boss won't let us..."
That's unfortunate. If your boss has explicitly forbidden you to write and run unit tests, then there's not much you can do. Let me make this absolutely clear: I'm not going on record saying that you should actively disobey a direct order (unless it's unethical, that is). I do wonder, however:
Why is the boss even involved in that decision?
It seems to me that programmers often defer too much authority to their managers.
A note on culture #
I'd like to preface the rest of this article with my own context. I've spent most of my programming career in Danish organisations. Even when I worked for Microsoft, I worked for Danish subsidiaries, with Danish managers.
The power distance in Denmark is (in)famously short. It's not unheard of for individual contributors to question their superiors' decisions; sometimes to their face, and sometimes even when other people witness this. When done respectfully (which it often is), this can be extremely efficient. Managers are as fallible as the rest of us, and often their subordinates know of details that could impact a decision that a manager is about to make. Immediately discussing such details can help ensure that good decisions are made, and bad decisions are cancelled.
This helps managers make better decisions, so enlightened managers welcome feedback.
In general, Danish employees also tend to have a fair degree of autonomy. What I'll suggest in this article is unlikely to get you fired in Denmark. Please use your own judgement if you consider transplanting the following to your own culture.
Technical decisions #
If your job is programmer, software developer, or similar, the value you add to the team is that you bring technical expertise. Maybe some of your colleagues are programmers as well, but together, you are the people with the technical expertise.
Even if the project manager or other superiors used to program, unless they're also writing code for the current code base, they only have general technical expertise, but not specific expertise related to the code base you're working with. The people with most technical expertise are you and your colleagues.
You are decision makers.
Whenever you interact with your code base, you make technical decisions.
In order to handle incoming HTTP requests to a /reservations
resource, you may first decide to create a new file called ReservationsController.cs
. You'd most likely also decide to open that file and start adding code to it.
Perhaps you add a method called Post
that takes a Reservation
argument. Perhaps you decide to inject an IMaîtreD
dependency.
At various steps along the way, you may decide to compile the code.
Once you think that you've made enough changes to address your current work item, you may decide to run the program to see if it works. For a web-based piece of software, that typically involves starting up a browser and somehow interacting with the service. If your program is a web site, you may start at the front page, log in, click around, and fill in some forms. If your program is a REST API, you may interact with it via Fiddler or Postman (I prefer curl or Furl, but most people I've met still prefer something they can click on, it seems).
What often happens is that your changes don't work the first time around, so you'll have to troubleshoot. Perhaps you decide to use a debugger.
How many decisions are that?
I just described seven or eight types of the sort of decisions you make as a programmer. You make such decisions all the time. Do you ask your managers permission before you start a debugging session? Before you create a new file? Before you name a variable?
Of course you don't. You're the technical expert. There's no-one better equipped than you or your team members to make those decisions.
Decide to add unit tests #
If you want to add unit tests, why don't you just decide to add them? If you want to apply test-driven development, why don't you just do so?
A unit test is one or more code files. You're already authorised to make decisions about adding files.
You can run a test suite instead of launching the software every time you want to interact with it. It's likely to be faster, even.
Why should you ask permission to do that?
Decide to refactor #
Another complaint I hear is that people aren't allowed to refactor.
Why are you even asking permission to refactor?
Refactoring means reorganising the code without changing the behaviour of the system. Another word for that is editing the code. It's okay. You're already permitted to edit code. It's part of your job description.
I think I know what the underlying problem is, though...
Make technical decisions in the small #
As an individual contributor, you're empowered to make small-scale technical decisions. These are decisions that are unlikely to impact schedules or allocation of programmers, including new hires. Big decisions probably should involve your manager.
I have an inkling of why people feel that they need permission to refactor. It's because the refactoring they have in mind is going to take weeks. Weeks in which nothing else can be done. Weeks where perhaps the code doesn't even compile.
Many years ago (but not as many as I'd like it to be), my colleague and I had what Eric Evans in DDD calls a breakthrough. We wanted to refactor towards deeper insight. What prompted the insight was a new feature that we had to add, and we'd been throwing design ideas back and forth for some time before the new insight arrived.
We could implement the new feature if we changed one of the core abstractions in our domain model, but it required substantial changes to the existing code base. We informed our manager of our new insight and our plan, estimating that it would take less than a week to make the changes and implement the new feature. Our manager agreed with the plan.
Two weeks later our code hadn't been in a compilable state for a week. Our manager pulled me away to tell me, quietly and equitably, that he was not happy with our lack of progress. I could only concur.
After more heroic work, we finally managed to complete the changes and implement the new feature. Nonetheless, blocking all other development for two-three weeks in order to make a change isn't acceptable.
That sort of change is a big decision because it impacts other team members, schedules, and perhaps overall business plans. Don't make those kinds of decisions without consulting with stakeholders.
This still leaves, I believe, lots of room for individual decision-making in the small. What I learned from the experience I just recounted was not to engage in big changes to a code base. Learn how to make multiple incremental changes instead. In case that's completely impossible, add the new model side-by-side with the old model, and incrementally change over. That's what I should have done those many years ago.
Don't be sneaky #
When I give talks about the blessings of functional programming, I sometimes get into another type of discussion.
Attendee: It's so inspiring how beautiful and simple complex domain models become in F#. How can we do the same in C#?
Me: You can't. If you're already using C#, you should strongly consider F# if you wish to do functional programming. Since it's also a .NET language, you can gradually introduce F# code and mix the compiled code with your existing C# code.
Attendee: Yes... [already getting impatient with me] But we can't do that...
Me: Why not?
Attendee: Because our manager will not allow it.
Based on the suggestions I've already made here, you may expect me to say that that's another technical decision that you should make without asking permission. Like the previous example about blocking refactorings, however, this is another large-scale decision.
Your manager may be concerned that it'd be hard to find new employees if the code base is written in some niche language. I tend to disagree with that position, but I do understand why a manager would take that position. While I think it suboptimal to restrict an entire development organisation to a single language (whether it's C#, Java, C++, Ruby, etc.), I'll readily accept that language choice is a strategic decision.
If every programmer got to choose the programming language they prefer the most that day, you'd have code bases written in dozens of different languages. While you can train bright new hires to learn a new language or two, it's unrealistic that a new employee will be able to learn thirty different languages in a short while.
I find it reasonable that a manager has the final word on the choice of language, even when I often disagree with the decisions.
The outcome usually is that people are stuck with C# (or Java, or...). Hence the question: How can we do functional programming in C#?
I'll give the answer that I often give here on the blog: mu (unask the question). You can, in fact, translate functional concepts to C#, but the result is so non-idiomatic that only the syntax remains of C#:
public static IReservationsInstruction<TResult> Select<T, TResult>( this IReservationsInstruction<T> source, Func<T, TResult> selector) { return source.Match<IReservationsInstruction<TResult>>( isReservationInFuture: t => new IsReservationInFuture<TResult>( new Tuple<Reservation, Func<bool, TResult>>( t.Item1, b => selector(t.Item2(b)))), readReservations: t => new ReadReservations<TResult>( new Tuple<DateTimeOffset, Func<IReadOnlyCollection<Reservation>, TResult>>( t.Item1, d => selector(t.Item2(d)))), create: t => new Create<TResult>( new Tuple<Reservation, Func<int, TResult>>( t.Item1, r => selector(t.Item2(r))))); }
Keep in mind the manager's motivation for standardising on C#. It's often related to concerns about being able to hire new employees, or move employees from project to project.
If you write 'functional' C#, you'll end up with code like the above, or the following real-life example:
return await sendRequest( ApiMethodNames.InitRegistration, new GSObject()) .Map(r => ValidateResponse.Validate(r) .MapFailure(_ => ErrorResponse.RegisterErrorResponse())) .Bind(r => r.RetrieveField("regToken")) .BindAsync(token => sendRequest( ApiMethodNames.RegisterAccount, CreateRegisterRequest( mailAddress, password, token)) .Map(ValidateResponse.Validate) .Bind(response => getIdentity(response) .ToResult(ErrorResponse.ExternalServiceResponseInvalid))) .Map(id => GigyaIdentity.CreateNewSiteUser(id.UserId, mailAddress));
(I'm indebted to Rune Ibsen for this example.)
A new hire can have ten years of C# experience and still have no chance in a code base like that. You'll first have to teach him or her functional programming. If you can do that, you might as well also teach a new language, like F#.
It's my experience that learning the syntax of a new language is easy, and usually doesn't take much time. The hard part is learning a new way to think.
Writing 'functional' C# makes it doubly hard on new team members. Not only do they have to learn a new paradigm (functional programming), but they have to learn it in a language unsuited for that paradigm.
That's why I think you should unask the question. If your manager doesn't want to allow F#, then writing 'functional' C# is just being sneaky. That'd be obeying the letter of the law while breaking the spirit of it. That is, in my opinion, immoral. Don't be sneaky.
Summary #
As a professional programmer, your job is to be a technical expert. In normal circumstances (at least the ones I know from my own career), you have agency. In order to get anything done, you make small decisions all the time, such as editing code. That's not only okay, but expected of you.
Some decision, on the other hand, can have substantial ramifications. Choosing to write code in an unsanctioned language tends to fall on the side where a manager should be involved in the decision.
In between is a grey area.
I don't even consider adding unit tests to be in the grey area, but some refactorings may be.
"It's easier to ask forgiveness than it is to get permission."
To navigate grey areas you need a moral compass.
I'll let you be the final judge of what you can get away with, but I consider it both appropriate and ethical to make the decision to add unit tests, and to continually improve code bases. You shouldn't have to ask permission to do that.
Comments
Before all, I'd just like to thank all the content you share, they all make me think in a good way!
Now regarding to this post, while I tend to agree that a developer can take the decision to add (or not) unit tests by himself, there is no great value comming out of it, if that's not an approach of the whole development team, right? I believe we need the entire team on board to maximize the values of unit tests. There are changes we need to consider, from changes in the mindset of how you develop to actually running them on continuour integration pipelines. Doesn't all of that push simple decisions like "add unit test" from green area towards orange area?
Francisco, thank you for writing. If you have a team of developers, then I agree that unit tests are going to be most valuable if the team decides to use them.
This is still something that you ought to be competent to decide as a self-organising team of developers. Do you need to ask a manager's permission?
I'm not trying to pretend that this is easy. I realise that it can be difficult.
I've heard about teams where other developers are hostile to the idea of unit testing. In that situation, I can offer no easy fixes. What a lone developer can try to do in that situation is to add and run unit tests locally, on his or her own machine. This will incur some friction, because other team members will be oblivious to the tests, so they'll change code that will cause those unit tests to break.
This might teach the lone developer to write tests so that they're as robust to trivial changes as possible. That's a valuable skill in any case. There's still going to be some overhead of maintaining the unit tests in a scenario like that, but if that overhead is smaller than the productivity gained, then in might still be worthwhile.
What might then happen could be that other developers who are on the fence see that the lone unit tester is more effective than they are. Perhaps they'll get curious about unit tests after all, once they can see the contours of advantages.
The next scenario, then, is a team with a few developers writing unit tests, and other who don't. At some number, you'll have achieved enough critical mass that, at least, you get to check in the unit tests together with the source code. Soon after, you may be able to institute a policy that while not everyone writes unit tests, it's not okay to break existing tests.
The next thing you can do, then, is to set up a test run as part of continuous integration and declare that a failing test run means that the build broke. You still have team members who don't write tests, but at least you get to do it, and the tests add value to the whole team.
Perhaps the sceptics will slowly start to write unit tests over time. Some die-hards probably never will.
You may be able to progress through such stages without asking a manager, but I do understand that there's much variation in organisation and team dynamics. If you can use any of the above sketches as inspiration, then that's great. If you (or other readers) have other success stories to tell, then please share them.
The point I was trying to make with this article is that programmers have agency. This isn't a licence to do whatever you please. You still have to navigate the dynamics of whatever organisation you're in. You may not, however, need to ask your manager about every little thing that you're competent to decide yourselves.
Thank you A LOT for putting words on all these thought. You'll be my reference whenever I want to introduce unit test.
My usual example is "a surgeon doesn't need to ask to the manager if he can wash his hand. Whashing his hand is part of his job". (Not mine, but I can't remember where it comes from)