What version controls don't solve: Semantic Conflict
martinfowler.com
martinfowler.com
Let's imagine I'm looking at that calculateBill method and realize that as well as calculating the bill, it also sends off accounting entries to the accounting system. I don't like the side effect, so I pull it out into a separate notifyAccounting method.
Before:
calculateBill: BillEntries -> Writer (AccountingSystem) Bill
...Jed's code:...
produceBillEntries >>= calculateBill >>= billProcessor
After: calculateBill: BillEntries -> Bill
notifyAccounting: BillEntries -> (Writer AccountingSystem) ()
...Jed's code:...
produceBillEntries >>= calculateBill >>= billProcessor
This yields a type error. calculateBill needs to have type a -> M b, instead it has type a -> b. If your type system can express concepts like "removed side effects" then the compiler will catch mistakes like "forgot to put the side effect back".Like anything else, feature branches can be abused, and require some discipline on the part of the developer to use correctly.
On git.git every feature is developed on its own branch. However, the maintainer integrates all the features into a single branch for testing and to prevent these sorts of semantic issues from creeping up. It's really only an issue where the feature branch is allowed to diverge too far from the main branch.
As a developer maintaining your own feature branch, there are generally two good practices to follow:
- Regularly integrate the main branch with your feature branch, review what's coming in from main, and test. Having a feature branch does not relieve you of being aware of what else is going on in the code base. Note that you'll generally do this on a throw-away branch, as you don't want to clutter your feature branch with test merges.
- Rebase your feature branch as needed if the changes in main invalidate any of your work so far. As part of rebasing, you'll have to account for those changes.
You can have a long-lived feature branch, the important thing is not to go into a cave with it. You need to keep it up-to-date wrt to mainline development.
For a shared feature branch, rebasing is a bit harder (but not impossible), so instead you will typically merge the main branch into your feature branch as needed. It is important to do these merges only when necessary if you wish to maintain a relatively clean development history, something that is important if you later need to track down the source of a bug or backout the feature.
Yes, static typing fixes problems with changing function signatures but that's only the first step of the way. The next step is to start thinking about and defining modules (files, classes, whatever) in semantic terms.
I haven't heard it discussed in a few years but the discussions around the Liskov substitution principle and Liskov/Wing semantic typing highlights the difficulties of adding semantics to everyday software development practices.
I think his point is precisely that VCSs cannot automatically avoid semantic conflict. He's not blaming the VCS, but rather saying that Feature Branch should be avoided even if you're using a modern VCS.
This only becomes apparent in the footnotes where he states that if your feature branches live for only a day, then your basically doing what he calls continuous integration.
This feels more like something that can be remedied through having good practices. Just like you shouldn't go without committing for days when working with Subversion, it is probably a good idea in a DVCS to:
- integrate the main branch with your feature branch often (at least daily)
- don't do semantic changes in a feature branch. Instead, switch to the main branch (or a very shortlived new branch) and do the semantic change.
This should avoid 2 feature branches doing semantic changes and then having a merging hell after a week or so of development.
(My understanding is that it happens too rarely in practice to make it worth the effort)
The only VCS I see mentioned is git in one of the footnotes regarding rerere.