I have given you the basics of Software Gardening in my previous columns. We have visited topics such as: why comparing software development to constructing a building is wrong; as well as talked about why you should treat software as a Garden and discussed soil, water, and light. Now, it’s time to begin looking at specific techniques, tools, and practices that you use often, perhaps every day, in Software Gardening.
Gardeners and farmers often store seeds from one season to the next. But they need to be stored properly or they’ll not be good the next season. The seeds may not germinate or the crops won’t be hardy.
This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free tutorials from experts
In Software Gardening, storing seeds is the practice of version control. But you must store the code correctly and use it correctly. If not, your harvest will not be good. In software terms, the release will not go well. Unfortunately, I have found after years of looking at version control practices and talking with hundreds of developers, very few shops do version control correctly, handling branching and merging in a way that makes harvesting the application difficult.
Software Branching practices
Before looking at branching and merging techniques, let’s cover some basics. First, no matter the size of your team, one person or many, you should be using some type of version control.
Second, notice I don’t call it source control. This is on purpose. Everything, not just source code, belongs in version control. Design documents, icons, notes, etc…anything and everything should be placed in version control.
Third, make sure you’re using a good version control system. If you’re still using Source Safe, I strongly encourage you to move to something else. Now! Visual Studio supports both TFS and Git right out of the box, both are good choices. Subversion is another good choice and integrates into Visual Studio through a third-party product. There are several other version control products that do a great job.
Finally, the frequency of check-in is important. It should be often, even several times a day. Once you get code working AND passing unit tests, it’s time to check-in. Do not wait for days and days. This actually makes things worse, as we’ll see in a moment. I break down each user story into tasks. When a task is complete, I check in.
One of the primary functions of version control is the ability to branch the code and then merge it again. There are several reasons why a team would branch the code. The first is physical separation of files, components, and subsystems. A branch would be made for each item that is physically separated from another.
Functional separation is another reason for branching. This includes features, logical changes, bug fixes, enhancements, patches, or releases.
Branching may also be done for environmental differences. For example, a change could be made to the build or runtime environment. A new compiler could be available. The application may use a new windowing system, third-party libraries, hardware, or operating systems.
How the team is organized may also be a reason for branching. It could be the activities, tasks, or subprojects needed to get the work done, are different enough that the team wants to branch. Or it could be how the team itself is organized into roles and groups, with each group wanting a different branch.
Lastly, a team may branch based on procedural needs. The team’s work behavior, company policies, processes, or procedures may cause branching.
Most of these are the wrong reasons for branching. In fact, when you properly implement Continuous Integration (I will discuss CI in a future column), branching is an anti-pattern. Let’s look at a common branching scenario (Figure 1), one that may be similar to how you branch on your team. (The reality is, the branching practices of many companies are more complex than this example.
Figure 1. A common branching scenario
In Figure 1, the line T is the trunk. B1 is a branch made to work on new features. Off of B1 are several branches, S1 to S4. Each is branched off of B1 and at some point in the future, merged back into the B1. But at some point, a critical bug is found in the released code, so branch B2 is made to fix the bug. Once fixed, it is released (R1). The fix must then be merged into the trunk, branch B1, and all its sub-branches that are currently in development (the lines coming out of R1). Now imagine if there is some sub-branch S5, that begins after S3 but finishes after S4 begins, but before it ends. Its changes may also need to merge into S1 and S2 (those lines have been left off to simplify the diagram).
Finally, the pre-defined release date, D is reached, so merge of S1 and S2 into B1 must happen. This means code that has been branched for a long time, and has lots of changes that must merge. Not only once, but twice, one for each sub-branch. Once the code for B1 is released, it must be merged into the trunk. Now imagine what a mess would exist if there were branches B3, B4, B5, etc, that must also be merged into the trunk, at the same time as B1. Note that B1 and B2 end with an arrow, indicating they never end. You maintain them for an unspecified period of time. This is an ugly mess and causes delayed releases, fewer features, and lower quality. There must be a better way.
Work on the trunk
The fact is, there is a better way. Always work on the trunk. At first thought, it sounds like this would create more problems, but in fact, it frees the team. The last minute merging doesn’t happen because the code has been merged AND TESTED, regularly along the way. The key caveat is the trunk must always and ALWAYS, be releasable. There are four ways to accomplish always working on the trunk: hide new functionality, incremental changes, branch by abstraction, and components.
Hide new functionality
Sometimes there are new features that you need to add that take so long they can’t be accomplished in a single release. Teams are tempted to branch, work on the branch over one, two, or more releases; then merge in when completed. This is what causes major merge issues. The correct way to handle this is to hide the new functionality.
The way you do this is to work on the branch, but turn the new functionality on and off through configuration settings. This makes the features inaccessible to users until the feature is complete. When the feature is done, remove the configuration settings. This way of working makes planning and delivery easier. Because you work entirely on the trunk, there is no branching, so your version control looks like Figure 2.
Figure 2: Working on the trunk eliminates awkward branching.
This is another technique to use when you have large changes to make and can be used in conjunction with function hiding. With incremental changes, you break down major changes into small parts and implement each on the trunk. Again, your version control looks like Figure 2.
Branch by abstraction
The next technique sounds like you make a branch based on the name Branch by Abstraction, but it isn’t. This technique consists of six steps.
- Create an abstraction over the code that needs to be changed
- Refactor the code to use the abstraction
- Create the new implementation
- Update the abstraction to use the new code
- Remove the old code
- Remove the abstraction layer if it’s not needed
You still work on the trunk, so it still looks like Figure 2.
The last way to work on the trunk is through the use of components. This technique is used in several different conditions:
- Part of the code needs to be deployed separately
- You need to move from a monolithic codebase to a core and plugins
- If you need to provide an interface to another system
- Compile and link cycles are too long
- The current code is so large and complex, it takes too long to open in the IDE
- The codebase is too large for a single team
Components are developed independently of each other. One component references compiled code of another and does not use the code itself. Version control will consist of several trunks, one for each component (see Figure 3).
Figure 3. Working with components
While components solve some problems, you have to be careful as they can cause some problems too. First, your application has components everywhere. Everything can become a component. Second, beware of God components that do everything or control everything. Third, you may be tempted to have a team responsible for one or more components. Instead, a team should be responsible for a piece of functionality. Finally, having lots of components increases dependency management. This can be reduced through good, automated packaging and deployment systems.
With all this talk about working on the trunk, you may think that’s the only option for handling code changes. There are still times when you may need to branch, but you need to do this in a way and time that make sense. I call this “Smart Branching” and there are three conditions where Smart Branching makes sense: branch for release, branch by feature, and branch by team.
Branch for release
It’s important to branch to indicate in your version control system where a release happens. This is one place where branching makes sense. As explained earlier, you still develop on the trunk. When the code is ready for release, create a branch and release from there. Critical defects that are found after release are committed on the branch, then merged into the trunk.
Figure 4. Branch for release
In Figure 4, the code is ready and branched as B1, then release (R1). At some point, a critical bug is found, fixed, and a new release is made (R2), then the code merged is into the trunk. The next release is then ready, so the code is branched (B2) for that release (R3). Another critical bug is fixed in B1, then R4 releases code to fix it. That fix is merged into the trunk, then down to B2. It then needs to be released R5 so customers have that fix.
One important note is that neither branch continues forever. At some point, the team determines that no more work will be done and the branch is ended. This is indicated by the bubble at the end of each branch line.
Some teams, as an alternative to branching at the time of release, opt to tag the trunk, then go back to that tag and branch when a critical bug is fixed. There is some discussion around this practice and opinion seems to be split about if this is a good practice or not.
Branch by feature
If you simply can’t work on the trunk, then you should branch by feature. In this scenario, each user story is a branch. The number of branches equals the number of user stories the team is currently working on. But no branch should be active more than a few days.
Testing is performed on the branch. When it passes QA, it is merged into the trunk. Changes to the trunk should be merged daily into active branches. Any refactorings are merged immediately.
Figure 5. Branch by feature
Looking at Figure 5, branches B1 and B2 are made for two user stories. B1 is completed and is merged into the trunk, then merged again into B2.
Branch by team
Branch by team is similar to branch by feature (see Figure 6). Remember that earlier I said if you have multiple teams, each team works on a feature. The primary difference is that branches are merged into the trunk, then immediately into other branches when they are stable rather than after passing QA.
Figure 6. Branch by team
One key to making this type of branching work is the teams must be small and independent. Large teams cause problems as there gets to be so many changes to the branch, that it becomes difficult to merge into the trunk.
Branching Best Practices
Before finishing up, here are some additional best practices to follow
- Compare before you commit. Make sure you have the latest changes and they don’t break your code.
- Build and test before every commit. Don’t break the build by checking in bad code.
- Build and test after every merge. Make sure you didn’t break someone else’s code.
- Explain commits. Add check-in comments so you know what’s in that change.
- Read merge comments from other developers. This keeps you up-to-date with other things happening in the project and may alert you to possible conflicts with your code.
- Group commits logically. The changes in each commit should be related to each other.
- Only store what’s manually created. In other words, don’t commit binaries. Especially ones that are generated from your code.
- Don’t obliterate. In other words, don’t just delete code without adding comments about what you did.
- Don’t comment out code. How many times have you seen code with dozens of commented lines. This is unnecessary. If you’re following good version control practices, you’ll be able to go back and find those lines if you ever need them back.
Hopefully now you’ll be able to improve your branching and merging techniques and in turn improve your code. The two key things you should remember are to work on the trunk and check-in often. By following the practices here, your Software will grow and be lush, green, and vibrant.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!