Although source control systems can detect and assist in the resolution of conflicting edits this is not sufficient to prevent developers from checking in incompatible changes. If the behaviour or interface of a type or collection of types is modified by a developer it is reasonable to assume that they will make appropriate modifications elsewhere in the system to account for these changes. However other developers working concurrently will not have these changes and will produce code that uses the old behaviour.

As an example, take the TestItem class from my previous post. Developer A modifies the signature of the CalculateResult method. Concurrently Developer C writes new code that uses the CalculateResult method in a class that was not previously using the TestItem type. As there are no files in common being edited the repository will not require a merge or otherwise detect that incompatible changes have been made. As a result when both changes are checked in there will be an invocation in Developer C's code of the CalculateResult method that will not include the new parameter. The code fails to compile, resulting in what is termed a broken build.

More subtly a developer may introduce changes in behaviour without changing the interface of any type. The code will still compile correctly but will not behave correctly at runtime. Solid unit tests are the first line of defence against this occurance, but other activities such as integration testing and inter-developer communication are required to fully address the risks of these changes.

As the source control system does not have any understanding of the code it stores this is not something that it can prevent. What is required instead is better process within the development team to make proper use of the strengths of source control while alleviating the weaknesses. I recommend the following process:
  1. The developer gets to a point where all expected unit tests pass and the code builds correctly.
  2. The developer gets the latest version of the code from the repository and merges their changes with it.
  3. If the developer already had the latest code they may check in immediately and the process ends.
  4. If the developer did not have the latest version (regardless of whether a merge was required) they must build the code, ensure the unit tests pass and validate any necessary functionality.
  5. Once they have verified that the merged changes are correct, return to step 2. This is necessary to ensure that no changes have been submitted during the build and validate step.
Following this relatively simple process will eliminate most broken builds and in my experience represents a good balance between risk and prevention. In many teams there is a social cost applied to the last developer to break the build. This may involve having to possess some kind of mildly humiliating object (such as a stuffed toy or tacky souvenier), make/fetch the team coffee or put a nominal sum into a jar to be used to buy the team drinks or take them to a movie. Whether this strategy will work for your team depends on its makeup and the culture of your organisation. I strongly recommend that any attitude that breaking builds is generally acceptable (outside designated and agreed events necessary for technical reasons) be squashed ruthlessly. Having a broken build can result in large drops in productivty as most or all developers are prevented from working as the system is not in a usable state.