There are many ways you can write your code.
We have best practices and linters, opinionated frameworks and architecture patterns.
The very big and very small decisions. Stuff you decide on the team-level.
Then there's a middle ground. That's what happens while you implement a larger feature or a smaller service by yourself.
The result is what your team sees. But the process is how you develop that code.
Everyone has their own way of writing code, and I believe we can learn from each other if we share our internal processes more.
Some definitions to start:
This seems like a fuzzy distinction, but with a larger example it'll make more sense.
You can find the rules of the game here: https://ccd-school.de/en/coding-dojo/application-katas/tic-tac-toe/.
In summary, we're developing a terminal program that displays a Tic-Tac-Toe game in text form. This program expects three inputs:
A0
or B2
for the player to place their symbolnext
, which starts a new gameexit
, which exits the programAfter starting up, it automatically sets up a new game for the player.
When the player makes a move, the game AI directly makes the opponent's move (which can be as dumb or smart as we want).
Let's look at how we can implement this horizontally or vertically.
Going vertical, our goal is to get a program running with a terminal loop. We want to start it; it asks for input; on enter it prints new text and asks for new input. Ideally, the text is our initial Tic-Tac-Toe field.
Development now would go as follows:
If you got this working, you can add state management, handling of keywords like new
and field names like B1
, add input validation, and so on.
When developing horizontally, we start with the "business logic". I'd use a test-driven approach and develop a class that manages our whole game state, takes inputs, and outputs the game text for the console.
Development plan:
TicTacToeEngine
TicTacToeEngine
toString()
on a new instance and expects the empty game state stringtoString()
overridemakeMove()
method with an allowed enum (e.g. Moves.A0
)makeMove()
method to pass the testmakeMove()
and then toString()
and expects an updated state stringYou can also create unit tests for invalid inputs, e.g. making the move "A0" two times in a row should throw an error. At one point, you should also write tests to expect an AI move (two changes) after making a player move.
When you got a fully working game engine, it is time to wrap it into a terminal app that instantiates a game on start (and on the new
keyword), listens for input and calls the makeMove
method with correct enums, outputs the state to console, and so on.
There are benefits to getting a fully working application as fast as possible. And there are benefits to having a small, tested, and reliable component ready for integration.
It comes down to preference and situation.
Horizontal development is only possible for small "horizons".
I prefer to start vertically by making "something" work, so I know where the different application parts interact and how the internal APIs look like. Then, if it makes sense (i.e. I have a well-defined component like a Tic-Tac-To engine), I go horizontally and implement one component fully.