Jotting #10: Branching Models and all that

2007-11-21

Proper software configuration management (SCM) is often treated like an unloved child in software projects. I am talking not just committing code into a repository, but about creating reproducible releases, merging code between code-lines and all those things that are sometimes boring but necessary to provide proper control over your team’s coding efforts.

Let me tell you what we did in our current project; I learned a lot during it, mainly since I had to manage the releases most of the time. In the end, the whole process is less scary than I thought; I’ve become even quite relaxed about it, and merging code is no longer scary (but a bit boring).

I must stress that you must adopt your own release process and fit it to your circumstances.

A few words on our software and its installation as it has some bearing on our choice of branching model. The code is a Java webstart application, written using Swing, connecting to enterprise beans on a server. This implies that whenever a user starts the application he will be forced to worked with the most recently installed version; i.e., there is ever only one release in production.

Here is our process to release (assuming we are currently at production version 2.2.1 and use the Linux convention for numbering):

  1. A set of features is defined for the next release.
  2. When the features are implemented a release branch is created and named (e.g., release-2.4).
  3. Part of the team, the release team, completes the new release code, incl. final configuration, acceptance testing, release notes, etc.
  4. The release team releases a candicate for acceptance testing on a test server (tagged 2.4.0rc1).
  5. Bugs in acceptance testing are fixed and a new candidate (tagged 2.4.0rc2) is released. This continues until the code is accepted.
  6. The code is released (tagged 2.4.0).
  7. Any upcoming bugs of the released code will be fixed on the branch line, repeating steps 4-6, and releasing the fixed version (tagged 2.4.1, 2.4.2, …)
  8. After each release (candidate), the code changes are merged back into the development line (merges are tagged appropriately).
  9. The development team starts to work on the next set of features on the development line, repeating steps 1-9 for the next release (2.6 or 3.0).

(If I have time I may add a picture of this process.)

What are the advantages that we obtained from this process?

  • We have a clear, easily understandable and reproducible release process.
  • There is no significant code freeze period when preparing a release.
  • The process allows the team to allocate time efficiently and in parallel.
  • The process is quite agile and flexible; there is minimal burden on developers as many can continue working as if unaware of the release process.
  • The code can be placed under coninuous integration at all stages.
  • We can reproduce production releases at any time quickly.
  • Even during the testing process for a new release (e.g., 2.4.0rc2), we can release an emergency fix for the current production version (e.g., 2.2.1 to 2.2.2) without major upheaval.

It is also obvious that our installation allows us to choose this branching model since we never have more than three versions out there: the current production (e.g., 2.2.1), the current release candidate (e.g., 2.4.0rc2) and the development line (named 2.5).

If your circumstances are different (e.g, customers paid for different feature sets), you will have to come up with a different branching model to make it fit for your needs.

Some recommendations:

  • Think early about the branching model and release process suitable for your project; at least no later than when the first feature set is complete
  • Learn and use some of the branching patterns (see references)
  • Merge early and often (before the deltas become too large and are hard too merge)

Don’t be afraid of branching and merging; once you understand the process, its limitations and benefits, everything beomes much easier.

References:

PS

Eric Raymonds has started a page on version control systems. Worth keeping an eye on it.


Jotting #9: To be checked or Not to be checked – That’s the Java Exception

2007-11-13

Always arousing some emotions (see this recent example), this question whether you should use checked or unchecked exceptions in Java. Other languages don’t have that “choice”, so this is a pure Java problem. Elliot Rusty Harold posted his rules about this problem, introducing so-called external and internal exceptions, some time ago in July. I think these are too complicated and unnecessary but I promised myself to write about it.

Let me come out with my point-of-view right now: Avoid checked exceptions, stay away from them. They are not worth it. OK so that’s out. But some clarifications are in order:

  • I do not argue against declaring exceptions in a method’s signature:
    • In Java you can list both, checked and unchecked, exceptions in the method signature.
    • You should document the most important contract violations that the method will reject.
    • Better still, document the contract in the spirit of B Meyer’s Object-oriented Software Design.
  • I don’t ask that rather obvious requirements, like arguments not being null, are documented in detail.
  • I argue against the rule that checked exceptions have to be declared in each method of the calling stack unless they catch the exception.

What is the main purpose of an exception? It’s to indicate a contract violation (I think Bertrand Meyer’s language is the most appropriate to use).

Does the method raising the exception care about the context in which it is called? No, the method provides its own context: its contract. The rules of ERH about internal and external exceptions make no sense whatsoever in this model. Whether I can write to a file (object) or not, whether the provided argument is of the wrong type or not, whether the object’s state is within the method’s control or not, is irrelevant and is not the question that is being asked.

The method asks a much simpler and more general question: Do you obey my pre-conditions? It does not care what the intent of the caller is, what the calling context or the semantics are. Only the programmer may know that. No, the method just promises to fulfil its side of the contract (post-conditions) if the pre-conditions are fulfilled.

If a file instance is passed as an argument, the file is always outside the control of the called method. If it cannot write to the file because the file doesn’t exist or is write-protected, the method doesn’t care. It just says: You’re not fulfilling the pre-conditions of my contract; here is my exceptional response.

The whole distinction between external and internal exceptions makes no longer any sense (and you could always argue ERH’s examples in different ways depending on context). So we established that any distinction between checked and unchecked exceptions is based on some arbitrary classification of the caller that cannot be maintained inside the method throwing the exception. (You can easily do this for any other model, the checked-unchecked cases are always based on such classifications.)

So we are left with the question whether the compiler-enforced rule on checked exception provides any benefits, and we have to ask the questions:

  • Is it beneficial that every calling method repeats the exception in its signature?
  • In a proper object-oriented design, who carries the information about the problem?
  • Where is the exception being handled and resolved?

The first question should remind you immediately of DRY: don’t repeat yourself. The answer to the second is obviously the exception itself, not the method signature. And the final one is more based on the experience that, at least in applications with a user-interface, it is standard practice to throw the exception back to the user rather than to resolve the problem programmatically. There are some exceptions (no pun intended) like in message-based applications where the queue may try to re-send the message a few times and finally succeed, adding some robustness, but ultimately the problem is thrown back to the user (or left on the dead-letter queue!).

Developers of embedded systems could tell you how hard it can be to resolve exceptions adequately to prevent the system from crashing. Sometimes they just reset the system and log the fault; sometimes they just skip the faulty data and continue (and interpolate, for example, when taking sensor readings). (You may want to read more on this issue and exception design & patterns in the recent series of IEEE Software articles by R Wirfs-Brock.)

In the end we are left with the conclusion that Java’s checked exception were an experiment of good intentions but one that ultimately failed and didn’t provide any significant benefits but rather gave rise to a new anti-pattern: the silently caught exception a la catch(Exception e){ /* do nothing */ }.