21 Commandments for Greenfield Development

A Developer's Guide to New Projects  

The “greenfield” project is known by developers as a productivity Nirvana. It is a beautiful place where there is no legacy, no bugs and endless possibilities. It is also littered with mines.

I’ve recently seen a number of anti-patterns around the creation and early growth new projects, and thought I’d summarize the anti-patterns I’d seen in the past as a guide for developers to avoid these common pitfalls.

1. Get a Pipeline in Place Early

Run your tests automatically. Deploy your project automatically. Getting your pipeline in place early is crucial to establishing good habits early and ensuring the health of your project is maintained from the very beginning.

Using continuous integration and continuous delivery is not just about testing your project and shipping it to your customers. Using CI/CD is also about codifying the way your team do things.

Deciding to switch from tabs to spaces in a meeting is all well and good but it means nothing unless it’s put into practice.

Adding a rule or a process is an instant way to put a team decision into practice. Once it’s in the pipeline there is no more discussion. There is no more ambiguity. It’s in there until you create a pull request to change it, and a change to the pipeline (and the team’s process) can go through code review just like everything else.

2. Use High-level Tests to Guide Development

One of the greatest techniques I’ve found for guiding a new project is the humble acceptance test. Tell the story of your simplest use case. Don’t build anything else until that story is a reality.

Start with an acceptance test. Make it as high level as possible. Imagine the simplest use-case for what you’re building and write a test that runs through that scenario in its entirety.

A good example of this might be when building a multiplayer poker server. Imagine the simplest poker hand ever. It would consist of just 2 players. They would not bet or raise, just call. There would be no pairs or anything complicated to deteremine the winner. The winner would go to the highest card.

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
step "The game is started"
{:ok, game} = Game.apply_action(game, {:start_game})
assert game.status == :pre_flop

step "The players pay small and big blinds automatically"
assert [90, 80] == Enum.map(game.players, &(&1.stack))

step "Both players can see 2 cards"
assert 2 == length(Enum.map(game.players, &(&1.hand)))

step "Both players check"
game = with \
  {:ok, game} <- Game.apply_action(game, {:check, @player1}),
  {:ok, game} <- Game.apply_action(game, {:check, @player2}),
  do: game
assert game.status == :flop

We could encapsulate this “simplest” game in an acceptance test and make it work. Making this test work, step-by-step and line-by-line is going to inform you what to build and what order to build it in.

Build that endpoint only when you need it. Add a database at the point where your acceptance test can no longer proceed without some form of persistence.

Note: If you are developing a plugin, a library or even a module, then your “user” is in fact your future self. In these cases you can write your acceptance test with the mindset “how do I want to be able to use this tool”. By designing a plugin or tool from this perspective, instead of diving straight into writing code, you may find you’ll build tools with nicer APIs and less bloat.

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
specify "Using failure codes" do
  step "I create a service that will fail with code :param1_error"
  instance = MyService.new(nil, 2)

  step "My generic failure block is not called"
  side_effect = double
  expect(side_effect).to_not receive :generic_failure

  step "My error-code specfic block is called"
  expect(side_effect).to receive(:param1_error_happened).with "no param1"

  instance.call do |on|
    on.failure { |result| side_effect.generic_failure result }
    on.failure(:param1_error) { |result| side_effect.param1_error_happened result }
  end
end

3. Use Low Level Tests as Sanity Checks

As you add components to the project, you can use your low-level unit tests as sanity checks. Even when rapidly prototyping, it’s generally a good idea to have a test for every component.

Regardless how vehement you decide to be regarding TDD, at the very least I would recommend the core uses of any component be unit tested.

These tests are able to guide your designs on a low-level, but are primarily useful for identifying the source of errors as early as possible to aid in debugging.

104
105
106
107
108
context "when the service was successful" do
  it "does not yield" do
    expect { |b| subject.failure &b }.to_not yield_control
  end
end

4. Don’t Over Organise

A group is not a group if it contains a single item. Similarly, a directory is generally useless unless it contains more than one file. Don’t create overly elaborate directory structures too early in a project’s lifecycle.

Keep things all together until your project pushes you towards a certain structure or abstraction. Let your structure grow organically and don’t be afraid to move things around when it makes sense.

Remember, no abstraction is better than the wrong abstraction .

5. Don’t Under Organise

By the same token, throwing files all into a single directory is also an anti-pattern. Uncle Bob writes that your application directory structure should communicate just as much meaning as your code does. Start thinking about creating bounded contexts early.

As Uncle Bob would say, make your directory structure scream .

6. Avoid Ownership

Avoid letting individuals work on the same area of code for too long. Without the visibility into others’ work you risk creating knowledge silos early. Developers will naturally gravitate towards areas they are most comfortable. Encourage peer programming in new areas to help build familiarity.

7. Kill Bad Patterns Early

The code in your master branch is not just the code that runs in production. It is the template from which all new code is conceived. It is acceptable, even desirable, to build naive implementations of future features. It should not be acceptable to build things in a naive way.

Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.

Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

You will want to be on the lookout primarily for bad patterns which obscure meaning. Later you may need identify patterns that cause performance issues, for instance.

8. Practice the Mantra of YAGNI

You Ain’t Gonna Need It.

The mantra of YAGNI tells you not to build things because you think you’re going to need them. Wait for the need to arise.

For example: If you’re adding user accounts to your database for a simple API. Don’t add the columns for Facebook, Twitter and Instagram OAuth tokens “while you’re there”. I don’t care how sure you are that we’ll need those fields in the future.

By the time you want to add social login and OAuth authentication to your project the concepts of a “user account” may have completely changed. It may no longer make sense to add those fields to the users table, as you may have accounts, users, devices and other entities in your design.

9. Create Surface Area

Development is hard when you’re constantly stepping on each others toes. It’s good to start by deftly crafting a series of high level components and structures which perform the rough of the project. Be naive and hard code dummy data until components can be fully implemented.

Building endpoints that return static data, or returning unstyled Lorem Ipsum “dummy text” is okay, especially if that content hasn’t been written or that interface hasn’t been designed yet.

With multiple developers elbow-to-elbow in the code, it’s beneficial to have those placeholders (and files) in place so developers have room to work without constant merge conflicts.

10. Default to the Lowest Opinion Option

Which linting configuration should you use?

Should we use tabs or spaces?

Which logging library is everyone’s favourite?

You and your team could probably spend an hour or more discussing the above questions (in fact you probably already have). The best way to get a project off the ground quickly is to go with the boring defaults and defer to precedent whenever you can.

Which linting configuration should you use? The one recommended by the linting library… The dead simple boring default. Now let’s move on with our lives. Please.

11. Avoid Discussing Ideas, Discuss Code

Nip long philosophical conversations about code in the bud early. If colleagues are arguing about which approach is better, ask them to shut up and build it. Throw it into a pull request. Discuss the result.

You might end up building a small thing twice, but you’ll avoid the time wasting of a meaningless and contextless debate.

12. Do Naive Things That Don’t Scale

As Paul Graham has said do things that don’t scale. Take the easy path. For now.

Don’t optimise too early and don’t shy away from third-party providers for services (even if you intend to implement those services in-house in the future).

Yeah, once we get to Google’s size it will be uneconomical to use Twilio’s API. That doesn’t mean we should build our own solution now.

“We can’t launch our product! We don’t have a support forum yet.” We also don’t have a single customer yet. Set up an email alias that sends through to the CEO. We can hire a support team when we get the customers.

Don’t build an elaborate tool for your back office when Microsoft Excel will do for now. It won’t scale to millions of users, but it won’t have to.

Software is exactly that. Soft. It is meant to change.

13. Avoid Technology for Technology’s Sake

I find it alarming how common it is for a developer or CTO to champion a new technologie simply “because it’s cool”.

Yes, it might help you hire a few passionate upstarts, but ultimately you’re just creating extra work for yourself.

Adopting newer technologies early is always a trade-off. If you are going to trade security and simplicity for something new, be aware of the reasons for doing so.

For the same reason you shouldn’t always want to make your hobby a job, it’s not always a great idea to bring your favourite technology into your business.

14. Avoid Technology for the Resume’s Sake

Even worse than above, adopting a technology for your resume’s sake can be even more destructive.

Be wary of developers who champion new technologies in the company and then try to “own” that part of the project. (See “Avoid Ownership” above).

If you adopt a new technology into the company make sure the team feel confortable with using it as soon as possible. Encourage asking questions in code review, and consider running mob programming sessions to onboard other devs and share skills.

15. Avoid the Unfamiliar, Unless it’s Ubiquitous

Similar to above, be careful about adopting things your team are not familiar with. If there is a technology or third-party service which you are considering adding to your project, try to make sure at least one or two team members have experience using it in a production environment.

If you are unable to ensure this then at the very least, try to ensure the tool is ubiquitous. For instance, maybe no one in your team has experience using a third-party software analytics & instrumentation tool. In this situation going with the ubiquitous service of New Relic is probably a better decision than going with something more obscure like Treo.

Consider using StackShare or counting Github stars to help inform these decisions.

16. Keep the Boring Things Boring

Don’t invest mental energy into the “boring” parts of your project.

Unless you’re trying to build logging software, you should not try and do anything fancy with your logging system. Go with the plain old boring approach so you can get fancy with your core competency.

17. Keep in Sync

As soon as you see a notification that your colleague merged a pull request, you should be stashing your work (or even better use a WIP commit) and pulling that sucker into your branch for rebasing.

Who cares if your tests aren’t passing. Get in sync.

The longer you go without seeing the latest changes the more disconnected you are with the current state of the project.

Get in sync and keep in sync.

18. Keep Strong Opinions Loosely Held

Strong opinions are fine. We don’t all have to be opinion-less yogis buliding software in an aura of agnosticism.

Strong opinions cause problems, however, when they are tightly held. You should grip your strongest opinions with the loosest grip.

This is up to every individual in a team to be uniquely aware of the biases and opinions they bring to the table, and to make special efforts to be most open to change the decisions that evoke the strongest emotional reactions.

Being overly dogmatic can put strain on team relationships and moral. Refusing to compromise can cause needless friction. No decision in tech is black and white. Every option comes with trade-offs. Roll with the punches and keep an open mind.

19. Don’t Neglect the README

READMEs are not just for Open Source.

Your project’s README is the portal into productivity. If a colleague can not pick up the README and become productive in your project without talking to someone then you have a productivity bottleneck.

Craft the README with love. Hold the reader’s hand. Use images and diagrams. Provide code examples.

Just because it might be a private, internal project doesn’t mean you shouldn’t provide a README.

20. Keep the Energy

Keep the energy and momentum in the project.

In my experience the best way to do this is to make small changes and merge often.

Throwing up a 5 line pull request, getting it reviewed in under a minute and merged to master feels good. Green ticks on your continuous integration pipeline feel good.

Give love and compliments in your code reviews. Bounce ideas off each other.

Get excited about each other’s work. If you see a colleague do something new to you, engage them and ask them about it. Embrace a change to learn from each other.

21. Don’t Burn Out

In a green field project in particular, be careful about burning out. When it’s so easy to add features, it’s also easy to get intoxicated with the rush of productivity.

In my early years of software development the most damage I ever did to myself was during these green field projects. I justified sixteen hour work-days and did myself serious harm. I ended up suffering from severe RSI and needed to take time off in order to recover my ability to type.

I honestly feared for my career those few weeks. It was genuinely terrifying.

So remember: take care of yourself.

comments powered by Disqus