Developer Coaching for Beginners

Yes, the title of this blog post is a little facetious, because the reality is that at many points in our careers – at many points in the average programmer’s week – we are coaching developers.

There’s a tale – possibly apocryphal – of a programmer who, when stuck on a problem, would explain it to a large inflatable parrot. The act of articulating the problem to someone else – even a parrot – often helps us to see a solution.

Yesterday, I paired with someone working on a library to compute visual flow. Machine vision is far from my area of expertise, so how could I possibly be of any help to someone – a PhD student in machine vision, no less – working on a problem I don’t understand?

The fact is, I coach developers working in problem domains I don’t understand all the time. I’m parachuted in – often at short notice – to pair with developers who are stuck on problems that I’m not familiar with. But if you think the role of a developer coach is to solve the coachee’s problem, then you’ve not understand my problem domain.

The trick to coaching developers is not to be a font of all knowledge. If a developer wants to know something specific, they can Google it. Rather, it is to help them articulate the problem. Yes, I am an inflatable parrot.

Don’t get me wrong: this is not an unskilled role. Coaches are perhaps more like counselors than inflatable parrots. But the essence of it is the same – to get them to articulate the problem they’re trying to solve, and to encourage them to envision a solution to try out.

This does require enough knowledge of programming, of practices, of processes, and of technology, to ask the right kind of questions. “And how does that make you feel?” won’t open the right doors in a programming context.

And, naturally, as a trainer in developer practices like TDD and refactoring, I also focus on keeping you safe while you go through the process of solving your problem.

I’ll notice if you’re not writing tests. I’ll notice if you’re making a lot changes before you run your tests again. I’ll notice if you haven’t checked your code in for an hour. I’ll notice when methods are getting too big, or when there’s a bit too much duplication, or when your method has Feature Envy for another class, and I will nudge you back to the safe path of frequent testing, frequent refactoring, and continuous integration.

None of these things will solve your problem, but they will protect you from car crashes while you solve it yourself. Yes. I’m not just an inflatable parrot – I’m an inflatable parrot who makes sure you’re wearing your seat belt.

Through Codemanship, my primary focus is on helping developers to build those good habits. You solve your problem. I help you develop the skills to do it safely and sustainably. My role as coach is not just to help you to solve your problem, but to help you emerge from that process a different programmer than when you went in.

I’m an inflatable parrot with an agenda.

Yesterday, I encouraged my pairing partner to write down in a single sentence what problem they wanted to solve. And then I asked them to write down the function or feature that would solve the problem – what would it do? That led us to tests that would fail because the code currently doesn’t do what he wants, and the act of writing those tests opened up a bunch of doors towards new ways of making it do it. We didn’t solve the problem in our session, but we constrained it with executable tests: the solution must fit in this box. And that’s progress.

Now think back to your working week: how many times did you play the inflatable parrot for a colleague? I’m guessing at least once. And that makes you a developer coach. Like I said, not an unskilled role, and it takes experience and quite a bit of confidence to get to a point where you can be parachuted into almost any problem or technology domain.

But – at a fundamental level – we’re all coaches.

 

Scheduled Online TDD Courses, May – July

I’ve got three publicly scheduled 2-day Test-Driven Development courses coming up:

I appreciate that we’re all going through some – how shall we say? – interesting times right now, and that many of you are getting used to working remotely for the first time.

Hopefully what you’re learning is that distributed teams can be as effective – sometimes even more effective – than co-located teams.

For every advantage of co-location lost, we gain another advantage when we remove the distractions of open plan offices, of long life-sapping commutes, and of never-ending meetings because we just happen to be in the same building,

And online training can be just as effective – sometimes even more effective – than onsite training. We are, after all, looking at and working on code. Screen sharing and webcams work just as well as sitting next to each other once you get the hang of it.

It’s pretty much the exact same training I do in person, only you can enjoy it from the comfort of your own home, without the daily commute either side and without the many distractions of being on your office. And the catering can be great – depending on what you’ve got in your fridge.

To find out more about the course, visit http://codemanship.co.uk/tdd.html

 

 

For Distributed Teams, Code Craft is Critical

Right now, most software teams all around the world are working from home. Many have not done it before, and are on a learning curve that means last week’s productivity won’t be returning for a while.

I’ve worked on distributed teams many times, and – through Codemanship – trained and mentored dozens of teams remotely. One thing I’ve learned from all that remote development experience is that coding discipline becomes super-important.

Just as distributed systems amplify every design flaw, turning what would be a headache in a monolith into a major outbreak in a service-oriented architecture, distributed working amplifies team dysfunctions as the communication pathways take on extra weight.

Here’s how code craft can help:

  • Unit tests – keeping the software working is Distributed Dev Team 101. Open Source projects rely on suites of fast-running tests to protect against check-ins that break the code.
  • Continuous Integration – is how distributed teams communicate their changes to each other. Co-located teams should merge their changes to the master branch often and be build aware, keeping one eye on other people’s merges to see what’s changed. But it’s much easier on co-located teams to keep everyone in step because we can see and talk to each other about the changes we’re making. If remote developers do infrequent large merges, integration hell gets amplified tenfold by the extra communication barriers.
  • Test-Driven Development – a lot of the communication between developers, and between developers and their customers, can be handwavy and vague. And if communication is easy – like on a co-located team – we just go around a few more times until we converge on what’s required. But when communication is harder, like in distributed teams, a few more goes around gets very expensive. Using executable tests as specifications removes the ambiguity. It should do exactly this. Also, TDD – done well – produces suites of useful, fast-running automated tests. It’s a win-win.
  • Design Principles – Well-factored code is very important to co-located teams, and super-duper-important to distributed teams. Let’s count the ways:
    • Simple Design
      • Code should work – if it don’t work, we can’t ship it. Any changes that break the code block the team. It’s a big deal on a co-located team, but it’s a really big deal on a distributed team.
      • Code should clearly communicate its intent – code should speak for itself, and when developers are working remotely, and communicating requires extra effort, this is especially true. The easier code is to understand, the less teleconferences required to understand it.
      • Code should be free of duplication – so much duplication in software is duplication of concepts. This often occurs when developers on teams work in isolation, unaware that someone else has already added a module that does what their module also does. Devs need to be aware of duplication in the code – Continuous Integration and merge awareness helps – and clued up to when they should refactor it and when they should leave it alone.
      • Code should be as simple as we can make it – every line of code that has to be maintained as another straw on the camel’s back. When the camel’s back stretches between multiple locations – possibly in multiple time zones – the impact of every additional straw is felt many-fold.
    • Modular Design
      • Modules should do one job – the ability to change the behaviour of a system by just editing one module is critical to a team’s ability to make the changes they need without treading on the toes of other developers. On distributed teams, multiple developers all making changes to one module for multiple reasons can lead to some spectacular merge train wrecks.
      • Modules should hide their internal workings – the more modules are coupled to each other, the bigger and wider the impact of even the smallest changes will be felt. Imagine your distributed team is working precariously balanced on high wires that are all interconnected. What you don’t want is for one person to start violently shaking their wire, sending ripples throughout the network. Or it could all come tumbling down. Again, it’s bad on co-located teams, but it’s Double-Plus-Triple-Word-Score-Bad on distributed teams. Ever dependency can bring pain.
      • Modules should not depend directly on implementations of other modules – it’s good architecture generally for modules not to bind directly to implementations of the other modules they use, for a variety of reasons. But it’s especially important when teams aren’t co-located. Taken together, the first three principles of modular design are better known as “Separation of Concerns”. Or, as I like to call it, the Principle of Somebody Else’s Problem. If my module needs to send an email, I shouldn’t need to know how emails are actually sent – all that detail should be hidden from me – and I should be able to work on my code without having to actually send emails when I test it. Sending emails is somebody else’s problem. It’s particularly useful in a test-driven approach to design to be able to write a test for code that has external dependencies – things it uses that other developers are working on – without actually binding directly to the implementation of that external component so that we can swap in a test double that pretends to do that job. That’s how you scale TDD. That’s how you make TDD work in distributed teams, too.
      • Module interfaces should be designed from the client’s point of view – tied together with TDD, we can specify modules very precisely from the outside: this is what it should look like (interface) and this is what it should do (tests). Imagine your distributed team is making a jigsaw: the hard way to do it is to have each person go off and make a piece of the jigsaw and then hope that they all fit together at the end. The smart way to do it is to define the shapes of the pieces as parts of the whole puzzle, and then have people implement the pieces based in the interfaces and tests agreed. You do this by designing systems from the outside in, defining modules by how they will be used from the client code’s POV. This also helps to restrict public interfaces to only what client’s need to see, hiding internal details, improving encapsulation and reducing coupling. Coupling on distributed teams can be very, very expensive.
    • Refactoring – the still-rather-too-rare discipline of reshaping code without breaking the software is the means by which we achieve good design. Try as we might to never write code that’s hard to understand, or has duplication, or is overly complex, or too tightly coupled, we’ll always need to clean up our code as we go. If the impact of poor design is amplified on distributed teams, the importance of refactoring must be proportionally amplified. The alternative is relying on after-the-fact code reviews (e.g., in GitFlow), which will become multiple times the bottleneck they already were when your team was co-located and you could just pop over to Mary’s desk and ask.

Underpinning all of this is a need for levels of delivery process automation – automated testing, automated builds, automated deployments, automated code reviews – that the majority of teams are nowhere near.

And then there’s the interpersonal: the communication, the coordination, the planning and tracking, the collaborative design. It takes a big investment to make a distributed Agile team as productive as a co-located team.

All the Jiras and GitHubs and cloud-based build pipelines and remote whiteboards and shared IDEs and Zoom meetings in the world won’t save you if the code craft isn’t up to snuff, though. It’s foundational to delivering as a distributed team.

If you want to know more about code craft, visit www.codemanship.com

 

Lunchtime Learnings in TDD

Most software developers are now working from home due to the COVID-19 pandemic. If you’re one of them, now might be an opportunity to hone your code craft skills so that when things return to normal your market value as a dev will be higher. (Data from itjobswatch.co.uk suggests that developers earn 20% more on average if they have good TDD experience).

We appreciate that things are a bit up in the air at the moment, so taking 2 days out for our TDD course might be a non-starter. And this is why we’ve split it into 6x 90-minute weekly workshops being run at lunchtimes.

The first workshop is TDD in JavaScript, which starts at 12:30pm GMT next Tuesday. Details and registration can be found here.

This will be followed by TDD in C# on Thursday, 12:30pm GMT.

Workshops in Python and Java will start the following Monday and Friday lunchtimes, so keep your eye on @codemanship for announcements.

New Refactoring: Remove Feature

If you’re using a modern, full-featured IDE like IntelliJ or Rider, you may have used an automated refactoring called Safe Delete. This is a jolly handy thing that I use often. If, say, I want to delete a Java class file, it will search for any references to that class first. If the class is still be used, then it will warn me.

Occasionally, I want to delete a whole feature, though. And for this, I am imagining a new refactoring which I’m calling – wait for it! – Remove Feature. Say what you see.

Let’s say I want to delete a public method in a class, like rentFor() in this Video class.

public void rentFor(Customer customer) throws CustomerUnderageException {
if(isUnderAge(customer))
throw new CustomerUnderageException();
customer.addRental(this);
}

view raw
Video.java
hosted with ❤ by GitHub

Like Safe Delete, first we would look for any references to rentFor() in the rest of the code. If there are no references, it will not only delete rentFor(),  but also any other features of this and other classes that rentFor() uses – but only if they’re not being used anywhere else. It’s a recursive Safe Delete.

isUnderAge() is only used by rentFor(), so that would be deleted. CustomerUnderageException is also only used by rentFor(), so that too would be deleted. And finally, the addRental() method of the Customer class is only used here, so that would also go.

This is a recursive refactoring, so we’d also look inside isUnderAge(), CustomerUnderageException and addRental() to see if there’s anything they’re using in our code that can be Safe Deleted.

In addRental(), for example:

public List<Video> getRentedVideos() {
return rentals;
}
public void addRental(Video video) {
rentals.add(video);
}

view raw
Customer.java
hosted with ❤ by GitHub

…we see that it uses a rentals collection field. This field is also used by getRentedVideos(), so it can’t be Safe Deleted. Our recursion would end here.

If the method or function we’re removing is the only public (or exported) feature in that class or module, then the module would be deleted also. (Since private/non-exported features can’t be used anywhere else).

But if customers can’t rent videos, what’s the purpose of this field? Examining my tests reveals that the getter only exists to test rentFor().

@Test(expected=CustomerUnderageException.class)
public void customerMustBeOverTwelveToRentAVideoRatedTwelve() throws Exception {
Customer customer = new Customer(null, null, "2012-01-01");
Video video = new Video(null, Rating.TWELVE);
video.rentFor(customer);
}
@Test
public void videoRentedByCustomerOfLegalAgeIsAddedToCustomersRentedVideos() throws Exception {
Customer customer = new Customer(null, null, "1964-01-01");
Video video = new Video(null, Rating.TWELVE);
video.rentFor(customer);
assertTrue(customer.getRentedVideos().contains(video));
}

view raw
VideoTests.java
hosted with ❤ by GitHub

One could argue that the feature isn’t defined by the API, but by the code that uses the API – in this case, the test code reflects the set of public methods that make up this feature.

So I might choose to Remove Feature by selecting one or more tests, identifying what they use from the public API, and recursively Safe Deleting each method before deleting those tests.