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:
B2for the player to place their symbol
next, which starts a new game
exit, which exits the program
After 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.
toString()on a new instance and expects the empty game state string
makeMove()method with an allowed enum (e.g.
makeMove()method to pass the test
toString()and expects an updated state string
You 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.