On his way to seniority, every junior software developer has to deal with commenting source code. Usually, it starts with following a recommendation from his favorite programming book. The novice developer is so exited that a program he has written himself, with his own fingertips, compiles, runs and breathes that he is eager to follow any advice from his book, even without clear understanding of reasons and consequences of his doing. So, comments like
i++; //increment i
But, after a while, the euphoria from a running software ebbs away, and the developer reflects on his programming practices. Finally, he finds out that software should be written not only for computers, but also for fellow programmers, be they other team members evolving the product, another team using the developer’s code, or even the developer himself in the future. People need more information; while computers must only know, what to do, people must also know why the software is doing what it is doing.
The junior programmer learns that comments are only one form of conveying of this additional information. Another, and more powerful tools are created as part of programming languages, such as type and module systems, member attributes, and access modifiers. Some development processes also contain practices allowing to convey additional information, from simple pair programming to unit testing or using of assertions and design by contract.
Some of these tools are dynamic, because they use source code to document the additional information. They have the advantage of verifiability. You can create a new unit, and run existing unit test against it. If the test is successful, it is an (incomplete, but nonetheless useful) prove of the fact that you know at least some of additional information needed to create a new unit. Or, in your code, you can use some members of existing class, and verify your knowledge that those members are intended to be called from outside by compiling.
That’s why turning-to-senior junior developers are so keen of using these tools thoroughly and in abundance.
Unfortunately, there is a drawback of such dynamic tools, indeed the fact you have to translate the additional information into the source code. Not only they increase pure development efforts (time spent typing expressions in a programming language) compared with comments or pair programming, but also they introduce a possibility for mistakes and incomplete translation.
Especially interfaces between software modules can suffer from such translation mistakes, because when the mistake happens (I say when, not if), it is often hard or impossible to fix timely, and it prevents solving a real-world problem. Recently, we had the following case.
In a client-server scenario, the client has to pass a user-ID to the server, as a part of a Url. We develop the server part, which is intended to be used with as many types of clients as possible. Depending on their architecture, clients can store user-ID as something simple like integer or Guid, or as something complex like e-mail, base64-token, or integers with some additional characters. Internally, we are ready to handle any kind of user-IDs. But because it has to be passed as part of the Url, and not every character is allowed in Urls, there can be a problem if some quirky client uses some prohibited character in their user-ID.
So, the additional information we have to convey about our server is, “you may use any kind of user-ID as long as it contains characters allowed in Urls. If a client has prohibited characters in user-IDs, it has to take care itself about it, for example by UrlEncoding the user-ID, or by replacing the characters manually”.
A well-intentioned developer decides that this is a good case where one of the dynamic tools can be used to convey the information, and proposes to create an assertion in the service code, checking user-ID to be either an integer or a Guid. While it would probably cover 80% of real-world cases, this solution introduces an issue for remaining 20%, with no good reason. The problem with this proposal is a translation mistake, because if you translate it back to English, it sounds something like “you may use a 32-bit integer or a Guid in one of the standard Windows formats for a user-ID”.
Please compare it with the actual requirement above and feel the difference.
The ultimate problem with dynamic tools is that not every information can be easily (if at all) translated. I’m a web developer in the last five years, but I’m not ready to tell you what characters are prohibited in Urls, without some heavy binging(*). Partially it is because you have to merge restrictions defined in RFC with real-world limitations of IIS, WCF and what not. Even if I knew all the evil characters, it would be not possible to check for them on the server side anyway, because when they are used, our checking code will not be called (what’s why indeed the prohibited characters are prohibited).
In fact, such translation problems are so common in real life that many developers are accustomed to them and live happily with the idea that a rough, incomplete, or unnecessary strict translation is still a usable translation.
In a sense, they remind me myself when I write my articles in English not really being proficient in this language. I don’t have a choice, if I wrote in Russian I’d lose many of you dear readers. But developers do have a choice how to convey the additional information. They don’t have to formulate them in a “broken” C#, especially in situations, when the benefit of verifiability is relatively small, and additional restrictions imposed by translation effects are relatively painful.
They only need some courage to say “no, I’m not going to assert or unit test that”, because their favorite book may have been saying the opposite, and they are used to trust in books.
(*) googing is out, binging is in?