Why branching is evil




















It shows how over time people tend to use metrics that highlight local optimums of a process because it is more comfortable. They just lose focus on their global bottom line. The book is about a production plant, but the analogy stands. Your feature branch is a local optimum with high-quality code.

It may also be so far off the main branch that it is of no use for the upcoming release. As the name suggests, in trunk based development the whole team pushes continuously to the main branch or use very short-lived 1 or 2 days max branches. Here is a detailed description of the idea.

I have no affiliation with the linked website. It is just a great overview of the concept. When you push your work to the main branch on every push, the amount of code to merge is way smaller and it becomes trivial. There is a far greater benefit though: you and your team can spot problems before they become painful. It might be that your refactoring clashes with another feature. Or you are drifting off from the project conventions or architecture patterns. This is the real value of the process.

As I preach in any place I find myself in:. Working days on code that will never get to the release on time is the biggest failure there is for a team. Another upside of pushing to the main branch is that your changes will run live in some environment.

It is always good to deploy and battle test your code, even in progress, in some real deploy. Use feature toggles. They can be environment variables or whatever suits you best to turn on and off your work in progress.

Make it defensive of course so your half-finished code does not get active in production by mistake. Your whole team will love this: you can activate the code on any environment at any time to see how it looks or performs. Testers can prepare to test early on. Unit tests. But I only write them for stuff that needs them..

Some complex math functions that translate between coordinate systems; the point of the unit tests is to confirm that the functions are doing exactly what I think they are doing. With mathsy stuff it can be very easy to look at the output of some function and think, that looks fine, but in reality its actually slightly off, and not exactly what it should be.

The unit tests are to confirm that its really doing what I think its doing. Acceptance tests by a human. Theres a spreadsheet of everything you can do in the game and what should happen. As we add features we add more stuff to this list. At regular intervals and before any release several humans try every test on various hardware.

Its super tedious but it has to be done imo. Automating this would be an insane amount of work and also pointless as we are also testing the hardware, you get weird problems with certain GPUs, gamepads, weird smartphones etc. I find those two types of tests to be essential, the bare minimum. But also anything in between, like some kind of automated integration testing is just a shittone of work and will only be useful for a relatively brief period of development, changes will quickly render those sort of tests useless.

Yes, totally agree. I've arrived at this exact same conclusion for frontend work as well. I always go for integration tests first, and only rely on unit tests if hitting some edge case is hard via integration test. And to clarify, if any individual function reaches some arbitrary level of irreducible complexity, then I'll absolutely unit test that. It's kind of a "you know it when you see it" kind of thing.

I find that, in this life, you usually get what you pay for, and, compared to other options, unit tests' primary virtue is that they're inexpensive. Unit tests help verify individual components of a system - which makes them top-of-mind for library code. I think the issue with them lies in that most developers aren't shipping libraries, they're shipping integrated systems, so there's no component worth testing.

At the same time, it's also genuinely hard to write good, principled tests of integrated systems, harder than it is to code up a thing that kinda-works and then manually debugging it enough to ship. You have to have the system set up to be tested, and feature complexity actively resists this - you fight a losing battle against "YOLO code" that gets the effect at the expense of going around the test paradigm.

How does this scale though? If you've got integration tests that include state, now you've got to either run your tests serially or set up and tear down multiple copies of the state to prevent tests from clobbering each other. As your project expands, the tests will take longer and longer to run. Worse, they'll start to become unreliable due to the number of operations being performed.

So you'll end up with a test suite that takes potentially multiple hours to run, and may periodically fail just because. The feedback loop becomes so slow that it's not helpful during actual coding.

At best, it's a semi-useful release gate. Is there another way? That is a very normal setup. This is called flakiness and is generally a symptom not to be ignored, as it is almost always indicative of bigger issues. It's rare that flakiness is limited to test environments. Instead it's much more likely that whatever your smoke tests are experiencing is a something end-users are also intermittently hitting.

Devs can write their own unit tests when working on their assigned tasks. Smoke tests are designed to run when you're trying to integrate those changes into the existing codebase.

At that point, you have the calculus all wrong. Smoke tests slow down devs enough that they don't merge broken code into production. That is a useful release gate unto itself. If unit tests pass but smoke tests fail, then often the vast majority of the time in my experience the issue is that either the dev didn't understand the task or, more often, didn't understand the system they were integrating into.

If you have some code that if its callers changed, they would stop using that code or use it on a different place, it's a unit and it's a good idea to unit test it. If you have some code that if its callers changed you would want to change it too, then it's on the same unit as the calling code, and it's bad to divide it away. We deploy to production multiple times per day, with no human intervention, successfully. Our test suite includes some unit and full end to end tests, and yea sometimes a breaking change slips through, we write a test to catch it next time, and move on.

Different projects have different complexities and differing levels of CD viability, but there are definitely many people truly doing automated continuous deployment all day every day. Besides less-anemic E2E tests, this is why type systems exist. Define a well-typed interface, code to the interface, test the units — and then it's hard to make terrible mistakes wiring things up. To realize the full benefit does, of course, require substantially better data structures than string-to-Any hashes, and it requires some proper old-school software engineering i.

And there is some overhead, though I'd say that a proper null-checking pass alone would be worth that much. Sorry, Java, your types are terrible. It works very well if you can tolerate that some of your users will have typically minor issues once in a while.

Maybe I'm missing something but when did continuous delivery imply no human testing? My last two jobs have had what I understand to be continuous delivery, to great success, but perhaps it was something other than CD. I don't agree that you can take humans out of the equation, particularly for new code. FANG push changes to each of many components daily, with feature flags. Trunk based can be sort of ok of you can accept a broken trunk and features can be kept small enough to give meaningful commits.

You need to make each step of each feature shippable if you use CD - so e. I make a point never to commit broken code anyway. If a series of changes have to be together to not break, I put them all in one commit.

Often a feature is made up of a series of non-breaking changes that can be layered one at a time, if appropriate. As a reviewer I really want a series of commits each making closely related changes that are easy to understand. How about with multiple repos, across multiple systems? You can't get them all in one commit then. Even with a monorepo not contributing back to upstream it'd be an utterly massive commit.

That negates one of the major advantages of committing code, which is having a regular backup of your work. My team makes several commits locally, then rebases, squashes, and pushes the squashed commit to the trunk which has CI. This maintains a local, regular backup of your code and collects features into single commits for the trunk which are easily revertable if found to be problematic.

Edit: Rereading your comment, I suspect you mean non-local backups. Our organization has a special remote only visible to you and those you allow for pushing code you'd like to backup off of your box. That sounds like feature branches but with more steps and worse. You can't share your unfinished work non-locally, for one thing. And it has none of the advantages of CI because you still have infrequent large merges.

I'm sorry but it hurts just to read this. Just No. Most spikes are not good enough to keep, so expect to throw it away. You are effectively prevented from releasing into production builds that are not thoroughly tested and found to be fit for their intended purpose.

Promiscuous Integration??? Trunk Based Development Break large changes into a set of small incremental changes. Engineers thought trunk-based development would never work, but once they started, they could not imagine ever going back.

Total views On Slideshare 0. From embeds 0. Number of embeds Downloads 9. Shares 0. Comments 0. Likes 1. You just clipped your first slide! Essentially if statements, they provide that ability, an elegant way of turning certain features on or off, without having to change and redeploy the code.

An application can determine the setting of a feature toggle using cookies, based on IP ranges, configuration settings, any number of sources.

There are many libraries that implement some form of feature toggles and even companies offering them as a service. Feature toggles allow us to make full use of continuous integration and deployment, spreading out a feature over multiple small commits.

Once a feature is complete, the toggle can be switched on. Feature toggles enable continuous experimentation, iteration and improvement. Be conscious of their number though.

A lot of teams use pull requests to do code reviews. One developer writes some code, submits a pull request and another developer reviews that code.

Simultaneously, automated checks can run tests and verify whether the pull request can be merged with the main line. Useful functionality, but pull requests are essentially branches, so those are out too! Code reviews are a very useful tool to maintain standards and quality. Pair programming enables continuous, real-time code review. Mix experienced team members with less experienced team members and you get transfer of knowledge as well.

Make sure pair partners regularly switch roles driver, navigator. I hope this post clarifies my views on feature branching, especially when viewed in the context of continuous deployment. He loves helping teams and companies to develop better software and significantly improve their delivery process. Of course, if you have enough code coverage, this should be discovered by your tests.

But then again, this is rework. And you still hope that your tests cover everything. Regarding the code reviews.



0コメント

  • 1000 / 1000