(test && commit || revert) Questions Answered
I gave an interview on TCR. Here are some questions with my answers.
What is TCR?
TCR stands for “test && commit || revert,” where the ampersands and pipes are essential. It is a Unix-style command, which first executes the test-suite of the project. If the tests are green, the code gets committed, if they are red, the code gets reverted. In pseudo-code, it goes like:
if testGreen():
commit
else
revert
When doing TCR, you replace your test command you already use, with this TCR command. As your code always gets committed when you run your tests and they are green. The revert-command, on the other hand, reverts your code to the last time your code was in a green state.
This reverting leads to an interesting change, how to write code. Every mistake in writing code leads to the deletion of the wrong code. This is quite frustrating in the beginning, but after some time, you change your behavior how you approach problems. You write a minimal, focused test, and just enough production code to make it pass. Then you refactor to a more general solution.
Whenever you write too much production code, you take a bet. Because the more you write, the more gets deleted and the more likely it is, that you make a mistake.
With this, TCR pushes you in the directions of the best practices, that you already know from TDD: Creating small, focused tests, fake behavior until you can refactor towards a general solution and taking baby steps towards the solution. When you do something else you increase the risk to lose your work.
Always green
But TCR is not TDD and there are differences to TDD. With test-driven development, you start by writing with a failing Unit-Test which leaves you in a red state. As the second step, you fix this test by writing just enough production code to make all tests pass again, so you go into a Green state. Therefore Test Driven Development is a Red-Green Workflow, at least regarding the state of the code.
In contrast, TCR is a Green-Green Workflow. As soon as you go into a red state, TCR kicks in and bring you into a green state again. This, however, means, that you can’t write a failing unit-test as you are used to in TDD. Instead, you have to write your failing unit-test AND write a fake implementation at once. With Fibonacci as an example, this goes like this:
Write Test:
assert fibonacci(1) == 1Before running the tests, add the Code:
func fibonacci(n):
if(n == 1) return 1;
When you created some fakes like this, you can refactor towards a more general solution. Because TCR is an “always green” workflow, where you can never slip into a state, where your tests are red, it shines when you do refactoring. When you do refactor, you always try to stay green. TCR and Refactoring are aligned here, and TCR supports Refactoring.
Git Prototype
So far, I always talked about testing, committing and reverting in a very abstract way. To get concrete you can just use the test-suite that you already use with TDD and Git for the committing and reverting. You for committing, you just do ‘git commit’ with a dummy message and “git reset — hard” for the reverting. Over the last half year, I used TCR with this prototype. I am sure, that someone will create a better tool, but so far it works surprisingly fine.
Which Downsides has TCR?
When you start with TCR, you immediately feel a potential downside: Your code gets deleted. However, after you practiced it a while, this becomes an advantage, because it pushes you into in the direction of the well-known TDD practices.
Bad beginning
Nevertheless, this experience of the thought alone, that TCR deletes your code leads to a lot of bad comments, for example on Reddit or Hackernews. But when you read them, it becomes obvious, that the authors never actually tried them.
Vanishing Test Problem
The second downside, you see, when practicing TCR is, that your failing test gets reverted as well, which is quite annoying. You write your test-fixture and the test, which can take some effort. Then you make a small mistake and everything gets reverted. This is no big deal for the production code, as you probably just written one line. But it is different for the test. This is called ‘the vanishing test problem’.
Typo revert
Another practical downside is, that whenever you write a typo, your tests fail and your complete code gets reverted as well. This is a big deal for me because I always produce typos.
Typo revert solution
I’ve created some variants of TCR, that I published on Medium a while ago, that is able to solve both problems. For the Typo-Problem, I created BTCR, which stands for “build && (test && commit || revert). The prefixing of TCR with a build-step abort the whole command if the projects don’t build.
The idea opens an interesting angle of view on TCR. I mentioned before, that TCR is Green-Green workflow, but what means green? There is a syntactical version of green, which is can be checked through the building of the project and a semantical definition of green, which is defined through the test-suite. I am aware, that the test-suite can never describe the semantic requirements on the software completely, but it is a very practical approximation because you can check it automatically — like the build. Back to BTCR. The original TCR requires syntactic and semantic correctness all the time. BTCR relaxes this to temporal syntactic incorrectness. It is temporal because you desire to get your code green on a syntactic level anyway because your code also does not get committed if you have syntax errors which lead to a bigger chunk of code, that probably gets reverted if you write too long.
Vanishing Test Problem Solution
The typo-problem was solved easily. It becomes harder with the vanishing test problem. It deletes your failing test along with your failing code. One obvious solution is to write very very tiny chunks of test-code. In practice, I’ve experienced, that this is not very practical, because you normally just have to set up some stuff at once.
Oddmund Stromme, the founder of TCR, suggested “pending tests” as a solution. Depending on your test-environment you set your test pending or skip and implement the code. When you are sure, that your production-code is correct, you remove the pending annotation. If the test is green, everything gets committed. If the test fails, the test fails, the pending annotation gets added automatically again, as the code gets reverted. The test with the pending annotation was already committed previously, so it does not get deleted.
There are other solutions, but most of them, including both, mentioned before lead to the ‘false green’ problem which is by far the most serious as it is a conceptional one and no one, that justs annoy you.
False Green
With TCR, you never see your test fail, because you immediately create a fake implementation. So you may ask, why TDD requires you to move into a red state before moving in the green. Doing this catches the ‘False Green’ problem. This means that your test-suite is green, but your code is semantically incorrect. One source is that the test doesn’t test anything. This happens often. With TDD, you immediately detect this, because you are not allowed to write production code when your test-suite is green. With TCR you possibly don’t recognize the bad test, as you think your fake made the test green.
The only way to solve this with TCR, I am aware of, is to negate the assert after your tests where already green and expect, that the test gets reverted. But I don’t find this very practical, as it requires a manual step with discipline. I normally forget stuff like this and many false greens slip trough.
As I practiced TCR in the past, I created a variant, that I called ‘The Relaxed.’ It only reverts the production code. This hurts one of the core ideas of TCR, as it allows you, that your code stays in a red state. You write a failing unit-test, run TCR and nothing gets reverted. TCR is not able to bring you back to a green state. But this sacrifice brings you the enormous advantage, that you can see your test fail and you can develop your test until you implement the production code.
‘The Relaxed’ is a good tradeoff for me because it does not change the way of working from TCR. You will not write more than one test before implementing them behavior, because your production code still gets deleted when you want to implement it. So you write just enough tests so that you can fake it with one line or so.
Steep learning curve
This brings me to my last downside of TCR. It is has a steep learning curve. To implement a real-world project, you have to be able to split your task into many little ones, that can be implemented with a few lines of code without losing the bigger picture and get stuck.
How to do this is already best practice in TDD. But in TDD it is optional. You can get started with big steps and migrate with more experience to some of the more advanced patterns like ‘London Style’ or ‘Chicago Style.’ With TCR, you have to do them from day one. Otherwise, you are not able to implement much more as Fibonacci or other pure functions.
Which Upsides has TCR?
Kent Beck did not invent TCR but popularized it in his most important article on TCR, published Medium. I think Kent told somewhere that a big advantage for him is, that it brought back excitement on programming and TDD. But I published an article recently on TCR where I reported my experiences from my real-world projects.
I conclude, that TCR is not exciting. And this is a good thing. You should not use a technique, just because it is exciting. For me, this is the case with microservices. You have a boring enterprise application where you have to create a customer management system — nothing special, more boring. But you could use microservices. Suddenly, the task becomes interesting again. A distributed system, a special CI environment, different database, synchronization and so on and so on. But you don’t create value with it, but add a lot of complexity and increase the cost for operation and maintenance as well the time to create the software. With TCR it is different. After you got used to it, you don’t recognize it much.
TCR pushes you into the direction of a very good TCR. Doing baby steps, strive for a big test-coverage and so on. The difference is that you have to have discipline with TDD to do it. Especially in late hours, I get undisciplined, skip tests, use big unstable tests or drop TDD as a whole, to rush in my free time.
With TCR, this does not happen, as the workflow constantly brings me back to the best practices. As soon as I drop them, my code gets reverted. When I use them, I don’t recognize TCR much. So you can say, that TCR is TDD with the discipline outsourced to a tool. You read a lot about discipline in TDD. I expect we will not read much about discipline in TCR as it is ensured by the workflow itself.
A nice advantage for me was that you have to stay very closely on a TDD-Strategy like London-Style of Chicago-Style to be happy with TCR. In the past, I had troubles to do this and always jumped around and created some tests here, some there. Without actively striving for it, I am very focused now and apply one or both constantly.
TCR pushes you to all the best practices you already know from TDD and don’t require discipline. This alone is for me advantage enough to use is. Especially because it doesn’t change too much in practical usage after you got used to it.
Another big advantage is the effect on collaboration, that becomes possible. After every commit, which happens every few seconds, you can integrate with your team. Limbo and Code-Sync are the keywords here.
Do you use TCR in your projects?
Actually, I do. I do it since the end of October 2018. I read the article from Kent Beck and just started to use it for some random Java Application for one of our customers. Back then, I thought, that many others started to use it as well, but I think there are not too many TCR-Programmers these days. Most of the programmers just use it in a tiny example.
Do you use it all the time, in every Project?
In the last four and a half months, I’ve created enterprise stuff, some web-apps, and a website. It works quite good if you have a language which builds fast and a project with fast tests. I also tried to use it in a C++ project where we create autonomous drones, but the build and test-times are just too big. The problem is that you can’t code in parallel with the tests running like in TDD because everything gets reverted if you are unlucky.
For what kind of projects is TCR good or bad for?
You can use TCR in principle for all projects where you can use Git and an automated test suite. But you only become happy in projects, where TDD makes sense and the build- and test-times are low. TDD makes sense, whenever you can say in advance what you want and when you can write an automated test. Therefore often prototyping, where you want to find for example a good interpolation function, and UI, for example, HTML/CSS is excluded from TDD and therefore for TCR. Doing both, I disable TCR. I try to separate my work very clearly between “TCRable” and “in TCRable” work. This works fine.