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.

A spectrum of decisions from small to the left to big to the right.

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.