If you are a Computer Science student, take heed:
When you're about to take on an assignment that takes more than two hours to implement, do Test-Driven Development. (that doesn't mean you couldn't use TDD in smaller assignments if you feel like it)
First of all, what is TDD? It is a philosophy that encourages you to create runnable tests before you write code that they test. Of course, the tests won't even compile, but the mere act of writing them forces you to think about your code and its interface - "How would I go about using this B-tree class I'm about to implement?".
TDD documents often refer to sophisticated unit testing frameworks to support automated testing, but forget about that for now. Focus on the testing. Write console applications that do stuff like if (MyTools.Sum(3, 5) != 8) throw new Exception("Sum is incorrect.") for all your relevant methods. If you write more complex stuff, such as a linked list, write tests that insert some items and then read them out, verifying that everything works as you expected.
Now, why?
So: Write a small chunk tests, then write (or copy/paste) code. Fix the code until the test passes. Then repeat until you're done. If you discover bugs not uncovered by your tests, write a test that fails because of the bug before fixing the bug. That way you'll never fall for that particular trap again.
Most of the reasons above can be used to argue for unit testing in professional programming as well. However, students should consider TDD doubly because: a) TDD saves lots of personal time and students suffer from no corporate friction to offset the savings, b) they are generally unfamiliar with what they're doing and c) they lack the general experience required to guess the location of errors, thus resorting to time-consuming WriteLine-style debugging.
If you try it and like it, learn to use a proper testing framework. It's easy when you already have the TDD mindset ready, and any automated testing environment will make your coding yet easier - even when you move up to professional coding one day. And yeah, it looks good on your CV. And we need more programmers that have been trained to think about testing and their mistakes right from day one.
You cannot trust a thing. Forget intuition. As I recently posted, you can't just go about thinking every date value is representable under the current culture. Well, there's a lot more, and I'm learning some of it the hard way. You just cannot assume a case-insensitive comparison between "bit" and "BIT" would be true. "What?" you say with utter disbelief - and with a reason. That's what I said at first, too.
It's the Turkish-I problem. In most Latin alphabets the small letter i capitalizes to I. In Turkish it's not so. The small letter i capitalizes to İ; (capital I with a dot - a glyph never even seen in English or Finnish!). And you guessed, "our" capital I isn't i in lowercase - it's rather ı, a small i without a dot.
As long as you're comparing user input, this doesn't probably make a difference. But what about your internal data structures such as configuration keys, table names and so on? Right... You may have buried bugs and even security holes by using string comparisons incorrectly.
Microsoft has an article on this and a few other issues in regard to .NET 2.0 - that's a good read. But once you know it, there's always another i18n surprise around the corner. Paranoia rules the day.
Next up: Hacking the stemming algorithm of a full-text-indexer to properly handle Chinese. <sigh>
Black hole projects versus teams with proper discipline - which one does your organization belong to?
The standard mantra for many programming evangelists is "use design patterns, they combine the wisdom of years for you to use" (in case you don't know what patterns are, read the Wikipedia article). Some of my recent adventures into the patterns world have convinced me that while patterns do contain much wisdom, they are not the solution to the average programmer. Why is this?
Design patterns help you model your real world object model into OO terms and avoid pitfalls such as tight coupling and unmaintainable code. That's great. But the problem? Proper use of the patterns requires so much preknowledge that the average programmer very easily stumbles on them. Certainly you can factor your real world application into some nifty classes, apply some Bridges, create Flyweights and throw in some Proxies for good measure. And then? Perhaps your code now adheres to some patterns - but it's not too likely to be much more maintainable: patterns are just new ways to play with toys you didn't understand properly in the beginning anyway.
Patterns come from the world of building architecture where they were considered extremely useful. Use of patterns in an all-professional environment is quite different from the current programming world. You can easily get a B.Sc level examination without actually knowing how to design an OO system. Now this is the problem we should be addressing!
Despite everything above, I'm by no means saying patterns are evil. Common terminology is good, and they do provide a valuable canonicalization for certain common phenomena in the software engineering field. However, they do remind me of software development processes: Once you're competent enough to really understand them, you realize you've arrived at most of the conclusions yourself.
A couple of weeks back, I was struggling with a set of Word documents that had external links to images (equivalent to the HTML img element). I desperately needed to get the image links broken and the binary image data included in the document. There were thousands of documents, so an automated solution was required.
Now, what's wrong with the following Word automation code (pseudocode, but it's the same in C#, VBA and whatever)? More specifically, why did it make the images vanish? For the record, practically the same code was experienced to work elsewhere.
foreach (Shape s in document.Shapes) {
if (s.LinkFormat != null) {
s.SavePictureWithDocument = true;
s.BreakLink();
}
}
It took us quite a few hours to figure this out, so I'll spill it for you. It's all about delays. When the links point to an HTTP URI, setting the SavePictureWithDocument to true seems to start a background thread that actually retrieves the images from the remote server. But since it does take some time (albeit very little), BreakLink is run and the document closed prior to completion of the download. Thus, the images disappear.
What made debugging worse was the fact that the code worked well when stepped in a debugger - the delays of me hitting the Step button were enough for the HTTP download to complete. I believe you can imagine the confusion until we figured this out. Though, in the end the ugly hack was easy: Just add a short delay (we used two seconds) before the BreakLink call. The correct fix would've been significantly more complex, so I never got that far for a one-shot application.
Now, which part of SavePictureWithDocument or BreakLink API documentation warns us about this behavior? You guessed it: None whatsoever.
Most coders aren't particularly famiiliar with threading and parallel computing issues. Any API that starts background threads without explicit request from the programmer is a risk to software stability. To some extent I can understand this in Word's case, but still, the behavior of the API is far from optimal. If the user has just set SavePictureWithDocument and then calls BreakLink, how likely it is that the user had just wanted to trigger the HTTP request but not use the results from it? BreakLink could quite well block in this case until the requests were done (although granted, this might be an issue with serious server lag). At any rate, a documentation entry on these issues would be highly welcomed.
Just another story from a land where lack of proper documentation (and surprisingly few hits from Google, too!) costs quite a few working hours.
I recently bumped into this:
foreach my $module ( grep( !/^Perl$/, $installed->modules() ) )
{
if ($module =~ /DBD::(.*)$/) {
$installed_DBD_modules{lc($1)} = 1;
}
}
For the non-perl-literate ones: The code block runs through the list of Perl's external code modules and collects the names of installed database drivers into a hash table. We now focus on the grep( !/^Perl$/, $installed->modules() ) part, which is the enumeration we're looping through. The modules() call (from an external code library) returns not just the list of installed modules, but also "Perl" itself. The grep statement is there to remove this, thus allowing the code inside the loop to just concentrate on the external modules only.
I see the grep statement as an abstraction layer. It hides (or attempts to hide) the fact that the data source also returns perl itself, not just external code modules. However, as it is implemented currently, I view the grep part as being nothing but unnecessary complexity - removing it wouldn't affect the result at all as the in-loop regex wouldn't match "Perl" anyway!
This particular abstraction layer has one problem: It doesn't really abstract out anything, as people cannot understand its operation without parsing the line in their heads. Now, if the grep statement and its contents were pushed out into a method such as "GetInstalledModuleNames()", things would turn out entirely differently. Also, it would make the code more easily maintainable as changes to the filtering method wouldn't affect the call site (or perhaps several of them) anymore.
Therefore, naming is essential. Often, naming means extracting code into methods. Don't avoid it. It's not a sin to have a method that gets called only once. Neither is it bad practice to have a method with but a few - or even just one - lines of code. Code length is irrelevant, readability rules. Most complex expressions (regexes, heavy boolean conditions, non-trivial string catenations etc.) are detrimental to code readability if embedded in a block of other code. Not so when they're well secluded in their own little methods.
Rule of thumb: Never mix trivial and non-trivial code in a single method. "GetInstalledModuleNames()" is always easy to understand, but its implementation may range from very easy (like here) to quite difficult (do the same in C++ for example!). The name is what makes a potentially complex operation trivial.
During the last few months, I've been delving into a huge lump of source code, trying to understand its structure and come up with ways to make the application more structured and expandable. I'm blessed: the code itself is actually rather decent, although the passage of time has certainly deteriorated the structure somewhat (this happens with all code anyway). It's been another great learning process, so I'll share some of the fruit: Jouni's five steps towards turning a huge mass of code into something you actually understand:
Print out a directory tree of the source. Run quickly through the code and make notes on what's in each directory (which classes, what sort of functionality). If you already have this sort of a document, great - but don't use it, do this yourself. Compare with the existing notes and see what you've missed. Be careful, as the old document might be somewhat outdated. Spend no more than one minute per file.
You should end up with a working knowledge of what's in the tree and what's not, although you probably cannot remember it all. Turn your structure document into an electronic form (unless it's there already) or make sure the existing document is up to date.
Ask around, take a look at mail archives and whatever you have. Try to find out what's wrong with the code, what kind of structural issues have been bothering people, how should the application in its whole develop and so on. Particularly, try to identify the "can-of-worms-bugs" - the ones which everybody constantly talks about but which never get done. Understanding the needs is important even if you're not planning on massive developments, as it often also relays information on how people actually use the code.
If you have a decent bug reporting system and people actually use it, you're going to have an easy time here.
This one takes time, but it's worth it. Go through every class and every method (perhaps not every line, but almost) of the code. Try to identify the major problems you found during the last step on the source code level. If you've heard explanations on why something is difficult to fix, try to understand the reasoning yourself. Make notes to support your memory.
Try to spot patterns. This is very hard unless you're an experienced programmer, but do it anyway. Try to identify the sorts of operations that repeat themselves throughout the codebase. Pay particular attention to these: Is the code required for a frequent task readable? Is it error-prone? Is it easily expandable? For example, if your frequent event is "open a database connection", how do you do it? Do you pass DB connection info and credentials around every time? What if you need to pass a special timeout value - could you do it? Is exception handling done the same way every time? Is it done in any reasonable way?
If you have special expertise, you can use this review round to spot other issues: security flaws, performance bottlenecks, globalization concerns, whatever. But! Don't make the mistake of thinking you'd find all the problems - f.e. exhaustive searching for security flaws cannot be combined with introductory review of an existing codebase. The same goes for most other non-mechanic hunts for code issues.
Allocate sufficient time. For me, it takes an hour to effectively go through 20-50 k of C# code, depending on the complexity of the operations involved. Your speed will vary a lot based on your experience and working habits. I repeat: This part takes ridiculous amounts of time. However, it will provide you with knowledge that considerably helps you in the next steps.
If we lived in a perfect world, this task would always be trivial. Automated build environments should turn this task into a no-op (by forcing the code solution/project/package to be very independent of any configuration), but it rarely happens. Even with a decent autobuild, there's often some work required in setting up your personal build environment. Be analytic. Why the requirements? Could they be removed? Is the process of doing a clean build straightforward enough?
Although the process of creating a build isn't particularly strongly related to the code itself, the inter-package relations (such as those implied by project references in Visual Studio solutions or whatever your build environment has) tend to become more clear by looking at the build process. Also, if you don't have a module dependency graph (which modules require which parts to build), draw one. Any format is acceptable as long as it's accurate. Again, verify that any existing documents are up-to-date before relying on them.
If the build produces warnings, note them. See if you can figure out the bad practices behind them. If the build produces errors, you're in for a world of hurt. Find a way to fix the issues now or you'll regret it. Return here when you're done. If a breaking build is everyday stuff in your dev team and you can't convince them into changing the habit, go on. It can't stop you, but you're still going to suffer. You have been warned.
After this step, you should have a working understanding (not just knowledge!) of how the software is composed (the modules) and how the modules themselves work (from the code review). You could've built the software earlier - actually, most of us do it as the first step. That may work, if you have a strong build environment and everything goes fine. But if you get errors and the build fails, it'll get frustrating. On the other hand, if you already know how the code works, it's likely that some exploration on the code errors will become a good learning experience.
Pick the most trivial of the issues you identified in step 2. Fix it. Make sure everything works thereafter. Repeat a few times, depending on the complexity of the bugs. The purpose of this exercise is not to enhance the product, but to provide a better understanding of how the software gets developed. If the bugs you fixed were more than typo fixes, you should've written or changed at least a few dozens of lines of code. Get somebody from the old dev team to review your changes so you'll get feedback.
The more sophisticated your build environment is, the more there is to learn in this phase. Take a look at all the metrics your code changes produced. If you have code churn analysis or unit tests, there will probably be quite a few interesting reports to scroll through, perhaps even some tests to write.
Once you're past this phase, there's little mental virginity left in your head for this project. Therefore, this is the last possible moment of writing up the ideas you got during the process. What frustrated you? Which parts of the code looked most dubious? Which patterns and practices felt uncomfortable? Raise discussion. Propose better approaches. File bugs. You won't have the same edge later on. Beware of the cynicism that naturally comes from the senior members of the development team.
Next up: Jouni's five steps to fixing the issues found in this process ;-) (no, not really, but I _will_ try to post more notes later on)
A colleague of mine pointed me at the presentation slides of Microsoft Architecture Days held during my holiday. Of particular interest for me was the presentation on Domain Specific Languages (DSL). The slides are in English, but the presentation itself (available as a video clip) is in Finnish. Although I had heard of DSL before, this was my first real introduction to the theme - and a good one!
In case you don't know what DSM (Domain Specific Modeling) is, here's a very short recap: It's all about about crafting a modeling tool (usually a visual one) for your specific need. It's different from UML-based modeling tools in several aspects. First, DSLs discuss the problem, not the solution. Instead of modeling with code concepts such as "This is a class", you model with domain terminology along the lines of "This is a login dialog". You then need a custom implementation - a code generator turning your model into a target language such as C# - for your modeling objects.
Second, the DSL has the semantics and behavior of your domain; if you're building a banking application, you use the terminology from the banking world. No, not just that; you use terminology from your banking application. This is different from most CASE constructs where the modeling language attempted to be universal, naturally killing much of the customizability. In the DSL world, you control every aspect of it. There won't be a 3rd party vendor stating that your dialog components must look like this, and the generation process can produce any artifacts you desire, ranging from compilable source to resource XML files and whatever.
Third, successful DSL cases have a common characteristic: All code is generated from the DSM, and there is no need for roundtripping (reverse engineering your code-level changes back into the model). You don't customize the end-language result - just as you don't hex edit the exe files produced by your compiler. You customize the generator to produce the right results.
Thus, domain specificity allows both a higher level of abstraction and more customizability. The drawback is, of course, that you need to tailor a language and a code generator for your purpose, and this is often a non-trivial task. It's not a particularly new issue though; for example NetHack has had a map-generation language implemented with lex/yacc for a long time. Most of us wouldn't touch those monsters though, so it's all about tools. Luckily, there's going to be DSL support in Visual Studio Team System - one more reason to wait for Whidbey. There are plenty of modeling tools for other platforms, so it's not a .NET thing.
The need for higher abstraction - describing problems instead of struggling with code concepts - is everywhere. Right now, we're resolving much of the issues by trying to build sophisticated class frameworks to support the development needs. It's an important path to take, but it certainly falls short. One of the problems is that as long as we work on the code level, we cannot avoid some of the necessary evils: even the best classes need instantiation, using directives, method calls, event handlers and whatnot. Another one is that even with a great framework, a lot of code is still needed for a complex application. Unless extreme discipline for code management is used, an application with a lot of code is rarely very readable. Also, domain-level problems ("Calculate the shipping costs for X") tend to vanish in the depths of code (lines and lines filled with logic such as "ShippingCostManager.GetWeatherVariables(x, WeatherController.CurrentWeather)").
Be it web page design, Bugzilla development, business reporting applications or whatever, this need for higher level modelling exists for most situations. DSM won't solve all the problems, and crafting your own language won't probably be the right way for simple one-shot applications. But for complex product families and software with long lifecycles, DSM has been reported to give 3-10 times increase in productivity. It's certainly a promise worth more investigation - the companies behind that sort of assertions were Nokia and Lucent, among others (see the presentation slides for more details).
For the working environment, DSLs could change quite a few things. It takes the expert programmers and architects to build the language, but thereafter much of the development is doable even with lesser technical skills. Used correctly, this might work as a way to get the client's domain expertise to a much better use. Possibly, it might even allow software companies to empower the visionary people with lesser technical ability. It can dramatically reduce the effort needed to guide new employees into the development; a problem-oriented development approach hides much of the technical details inherent in all complex applications. It could help architects to enforce the patterns they now distribute as documents and presentations; with a DSL, you probably couldn't do the wrong things.
It would be irresponsible to conclude such a positive post without a pessimistic note. There is no silver bullet. But objectively taken, Nethack's map generation language certainly makes much of a difference in creating action-packed special levels for the game. There, the small DSL used is an enormous time saver. If the language generation tools advance well beyond the infamous lex/yacc pair, the initial cost of using DSL could drop drastically. The concept of high abstractions and custom languages make most architects wet their pants in excitement. The need for this technology is evident. As I already said, it's all about tools.
Additional info: DSMForum.org, Microsoft DSL Tools page.
Douglas Reilly recently posted an article called The value of experienced coders discussing the merits of experience and the short-sighted approach of pushing experienced developers either to sales or management. A rather interesting read, and luckily quite a bit shorter than my usual recommendations :-)
Not that I'd be close to even 40 yet, but the article did get me thinking. What is the career future for an aging software developer? Will the young necessarily replace him? How should the old fart make sure he gets to keep his job and stays valuable? Is he eventually forced to choose between his profession and glory?
I've had the pleasure of meeting some real professionals from various fields, ranging from metalworks and farming to coding and legal profession. None of the people whose skills I admire most work as managers or in sales - they're simply solid operators - might I say artisans - in their own field. It's a real shame if the software industry wastes this experience and potential in the search for short-term gains.
Even if it's evident by nature, I'll say it again: The industry needs to focus on building more diverse career paths. The industry needs the weight of experience to keep this thing in balance. While the youngest among us provide unparalleled energy, motivation and ideas, they are far too uncertain of themselves and their role in the world to build good and long-lasting products.
While I've been exploring in the unit testing world for quite some time already, the recent weeks have been another great push in this direction. The techniques and patterns for unit testing are relatively easy to comprehend. My real challenge is business integration and managing the developer-driven part of the testing process: The ways to teach people on it, the ways to measure the success of it, the ways of making test automation a useful tool right from the start.
In my quest for truth, Alberto Savoia's The Developer Testing Paradox has been a great read. It's long and heavy, but very well written. It'll take you half an hour to read and digest, but trust me: it's one of the more useful half-hours you've spent recently. This applies even to those people who aren't unit test nuts - it's still a good idea to understand this emerging facet of software development.
One critical part in good-quality code is the way it handles errors. Error handling is very rarely fun to write, but nevertheless, it's one of the things that your code gets judged by. Or, rather: proper error handling rarely gets praise from the end user, but the lack of one causes lots of irritation. The correct approach to error handling is, naturally, very dependent on the nature of the application and the tools used. The following assumes having an exception propagation mechanism (the try/catch/finally constructs available in most modern languages).
The fundamental problem can be formulated like this: The sum of your ability to pinpoint the exact source of the error and your ability to react to it meaningfully is constant. When you're deep down in your call hierarchy, perhaps executing a SQL statement for saving the state of some business object of yours, you can easily tell what happened and where by examining the exception. However, what can you do about it? Retry perhaps, but even that may not be correct at times. You shouldn't pop up dialogs or write to the console either - you don't know the conditions you're running under, and the UI is the caller's responsibility anyway. Well, you let the exception propagate upstream.
Now, you're back in the UI calling code (let's say a button click handler). The exception arrives from your business logic library. Now what? At this point, it's easy to give a meaningful error message ("Saving the foobar failed because of a database error") - but giving useful advice for the next step is harder. Should the user retry? What is the likely cause for the error? Some applications dump the provider error message (such as the SQL Server error, usually available through the exception object), but they're pretty likely to be extremely confusing to the user, and possibly even in the wrong language.
It's obvious that just rethrowing the technical exceptions doesn't give you many good choices at the UI level. One reasonable alternative is packaging all the additional information you have into a custom exception class whose InnerException property (something like this exists in every class library) contains the original, technical error message. This way, you can go into extreme lengths of providing context information. The example below illustrates the concept (pseudocode very reminiscent of C#).
public enum DBOperationType { InsertFoobar, UpdateFoobar, DeleteFoobar }
public class MyDBException : Exception
{
public enum DBOperationType FailedOperation;
public bool ShouldRetry;
public string SqlStatement;
}
// inside a class
...
public void UpdateFoobar(Foobar f)
{
string sql = CreateFoobarUpdateSql(f);
try
{
ExecuteNonQuery(sql);
}
catch (DBException d)
{
MyDBException md = new MyDBException();
md.InnerException = d;
md.SqlStatement = sql;
md.FailedOperation = DBOperationType.EditFoobar;
md.ShouldRetry = IsTimeoutOrDeadlockVictimError(d);
throw md;
}
}
Now, you'd have considerably more information at your disposal when catching a MyDBException in your UI code. You can access the original DBException through the InnerException property.
Another important rule: Never catch what you don't understand - or at least rethrow it. The code above only catches DBExceptions. This is usually desired, as catching everything á la catch (Exception e) { ... } very easily hides errors. For example, a NullReferenceException caused by one of your code bugs could be nastily hidden by a catch-all exception statement. For the example above, ExecuteNonQuery is unlikely to cause such situations, but for the typical "executereader and loop through, constructing objects into an arraylist" is a totally different case with vast error possibilities.
Of course, if you just analyze some aspects of the error and then rethrow it, there's no problem in catching everything. But don't make the mistake of assuming all errors are acceptable even if some are. I've seen quite a few examples where the coder had thought something like "Writing this log entry to the DB is non-critical - thus it's OK if I just swallow all the timeout exceptions with an empty catch block". Yeah right - one day somebody breaks your logging SQL, sending you into a situation where nothing _ever_ gets logged and you have no way to find out (until you - or the end user - look at the database). Point being: Even swallowing all exceptions of a certain class isn't usually a good idea! Something like the following may be acceptable, though:
try { ... }
catch (DBException d)
{
if (!IsTimeoutException(d))
throw;
}
All that said, it's of course a good idea to catch all exceptions at the topmost level of your code thread (usually the UI code), as not doing so may result in an abnormal termination of the program. Even then, silently swallowing exceptions is a bad move. It almost always bites you back.
The stack trace is fundamental to the debugging process. The trace shows where the error came from, and with the proper tools, it can also tell a lot about the program flow. However, there's a caveat. In C#, using the throw statement works a bit differently from f.e. Java (but like C++). When you've done catch (Exception e), there's a difference between throw; and throw e;. They both result in a very similar exception being thrown, but with one key difference: the parameterless throw ("rethrow") preserves the stack trace, while throwing the same exception again makes it look like the exception originated from this method.
It's extremely rare for you to actually want to mask your exception's true source, although it is theoretically a good idea for encapsulation purposes. For example, a public method of a tool library might want to hide the internal implementation details - after all, they don't belong to the caller. In this case, catch (Exception ex) { throw ex; } would do the trick. Anyway, it's not worth it most of the time - the harm made for debugging is too big. When you receive your first bug reports from the end users you'll regret the hiding. Obfuscation works better.
Most cases of "throw ex" are the result of programmers not knowing about the parameterless throw statement. Even when used intentionally, I consider it an anti-pattern that should ring your warning bells.
Proper exception management is somewhat cumbersome. Often writing it first (with the code) results in wrong design decisions. Typically you end up catching and handling exceptions at too deep a level. I've recently taken the path of first writing the whole call graph for a single operation (the simple example of updating something at the DB will do here) from UI to DB, then going back to the lowest level of code and starting upwards, thinking of possible problem scenarios and how to catch them properly.
This approach provides a rather good end result; the catch constructs are usually placed somewhat optimally, as long as care is taken to refactor them when more call sites join the graph. However, the price is high. At first there exists a preliminary version of code where very little error handling is made. Now, if I forget to go through some branches of the call graph, I may actually end up checking in code without error handling, which is a rather undesirable event. Thus, the rule of "never write code without proper exception handling" is justified, even though it tends to result in need for refactoring later on.
As one solution, I've been trying to write unit tests that would clearly indicate the presence or lack of error handling code. This way, I could formally check for proper exception handling even if I don't write it at the same time with the normal implementation logic. However, particularly with DB objects and methods, unit testing is already complex enough as it is. Ideas on improving this process, anyone?
ScottGu of MSDN has resurfaced in the blogging world, giving us two excellent articles on ASP.NET 2.0 development processes. First "Whidbey Update" covering some of the internal metrics and scheduling issues, and then "Testing ASP.NET 2.0 and Visual Web Developer" about the testing procedure and platform in general.
Some of the that stuff is truly well-polished and that testing farm of 1,200 computers is impressive. While none of that is a guarantee for quality, at least there is a tremendous amount of testing effort behind the scenes. And perhaps there's something to be learned from the processes as well (even though most of us don't work with software projects of that size).
I was particularly delighted by Scott's note on reviews: "All code checkins must always be peer-reviewed prior to checking in (this is even true when the most senior developer checks in)." The more experienced I get, the more I believe in peer reviews. For many sorts of issues, good review policy beats (not "replaces", though) many testing mechanisms hands down.
Came across an interesting CodeProject article: a .NET application that takes a database connection string and deletes all information from all tables except those specifically marked with IsStatic attribute. Take a look at
Database Resetter.
The application is not finished yet, and crash on a table's self-reference is pretty serious... but I like the concept. The ability to easily wipe out your test DB and start over again with an empty DB is really valuable at times. Unfortunately for some, it only works on SQL Server.
Last week I spent one afternoon listening to Redmondian propaganda at the Microsoft Challenge event (sorry, the site is in Finnish only). The MSDN track was mostly web services - not particularly surprising when considering .NETs fundamentals and the whole Connected Systems hype.
My personal surprise came in when one of the speakers asked the audience about who had used web services for more than a trivial demo application. The result of a quick count was 30% - so that means we have 70% of people who still haven't seriously touched SOAP, WSDL and whatever WS related stuff. I was genuinely surprised - I mean, the event was aimed at ISVs developing for MS platform, mostly even MS Partners. While it would be exaggeration to say the developers in that room were the cream of the Finnish crop, it was a fairly professional bunch with considerable experience in various fields of software development. Microsoft has been pushing hard for web services for quite some time, and we're still seeing numbers like this.
Usually developers grab new technologies as their toys first. RSS is now passing that stage: while it still remains a hip thing to play with, it also has huge number of practical apps. In a few years RSS will be as mundane and pervasive as HTML is now. But despite the long history of SOAP, why isn't playing with web services cool? Despite the fact that basic web services are very easy to create on almost any net-capable platform, most people seem to circle around them. Open source projects rarely contain web service interfaces despite their potential (Bugzilla's lack of them pains me greatly, both in practice and in principle). People write their own blog engines, forum software, image galleries... Yet still, I very rarely see anything that would enable other systems to connect to this brand spanking new application. Hmm.
Is it because WS world is driven by capitalistic entities such as Microsoft, BEA and IBM? Or is it because the amount of WS-* standards is so baffling? Agreed, the hassle around modern web service enhancements has buried the fact that basic SOAP has worked quite well for years already. Stuff like StrikeIron's web services are a small but nice example of what can be easily done with WS.
But I believe in the toy-first propagation model. And despite the multi-billion efforts by huge enterprises, what the world needs is more stuff like QuotesService. Practically useless, but simple enough to give others a starting point.
Anatoly Shalyto, a professor at Saint-Petersburg State University of Information Technology, Mechanics and Optics recently published an article about Foundation for Open Project Documentation. If you're interested in reading another set of philosophy about the importance of documentation in general, go ahead and read it. I didn't find the article itself very interesting or evolutionary, but it did give rise to a couple of thoughts.
First, I can't remember when I last saw a project that had a really admirable quality of documentation. If the user manual and installation instructions are well written, projects usually get praised... until somebody has to pick up the development ball after a three-year break during which the original code authors have switched jobs. Technical specifications are often limited to the few structural diagrams drawn during the analysis phase and a hastily sketched DB diagram/document that's roughly maintained throughout the project. Who said documentation is only for customers (or worse, end users)?
Second, I can't remember when I last saw a developer with a really documentation-oriented approach to software engineering. By "documentation-oriented" I refer to the desire (and discipline) to make sure that the brainwork is actually recorded in a reusable form. Writing good, readable code is an important step. Using proper design patterns helps, too. But does all this produce a really durable solution? Not without documentation. Not even the geekiest developer is better at reading C++ than plain English (or whatever your preferred language is).
Code-technical documentation - by which I mean documenting very simple issues such as namespace/class relations, chosen algorithms, design patterns and so on - has lately been very close to my heart. I'm certain I'll be returning to the subject sooner or later. When I look at f.e. Bugzilla, I see a huge need for somebody to explain the code structure in both diagrams and words. If we had a proper technical documentation, developing BZ would be so much easier for new people - and we'd probably have less bugs as well.
Until I've formulated another rant on some practical approaches in code documentation, I'm sticking with this motto: Mistakes in Word are cheaper than mistakes in Visual Studio. Feel free to substitute your favorite documentation/code editors. And I'll finish off with this citation from Shalyto's article: 'One of the pioneers of the aircraft D. Douglas said once - "when the weight of the documentation becomes equal to the weight of the airplane, it is ready to fly", and A. Martin affirms, that the documentation for the famous Boeing 747 weighed more, than the plane itself.'
Microsoft's Gunnar Kudrjavets mentions the Mozilla Security Bug Bounty (get $500 by finding and reporting a security bug) and ponders the effect of money in the bug reporting process. Naturally, Gunnar's concept of a bug-finding competition with some teams getting money and some not is pretty far from everyday life. In truth, few people search for bugs. The reward would have to be really disproportionate to change that. But if you bump into a bug, the additional incentive just might make you report it.
Thinking of it in terms of probability, if your chance of getting the bounty is 1/100 per hour of work done, the expected value is $5/hour. Not many people would bother - at least not those with enough ability to actually go looking for security bugs. However, once you've discovered something you think that could qualify as a security bug (by pure chance, in your daily use), things change. If you can write the bug report in half an hour and there's a 50-50 chance of getting the prize, you've just netted yourself an expected value of $250 in 30 minutes. Most of us would take that opportunity.
So, paying for bugs mostly encourages reporting, not finding. However, from the software developers' perspective (and for a product with a sufficiently large user base) those two things end up being very close - it's likely every bug will be encountered by someone, and it's just a question of which ones get reported.
To return to Gunnar's original thoughts on the competition: I'd like to see it tested as well. I'm not certain money would make that much of a difference. The people most adept at finding serious bugs are probably more thrilled by the competition than possible money involved. Thus, my hypothesis is that if Gunnar's teams are top-of-the-field in the technical sense, money will be less of an object. If the teams consist of people who are less driven by ESR's hacker attitude, money might affect motivation enough to make a difference in the results. And of course, in the long term, if we're talking about how much QA organizations get paid for their daily job, money will become a motivating factor even for the technically most advanced teams.
Yet another article (this time by Martin Fowler) preaching on the benefits of continuous integration. For those of you not familiar with it, it's a software development principle that says you must have an automated build process that verifies the functionality of the source code several times a day. So, at all times, code in your source control must compile and must pass the predefined simple tests (like the application must start properly etc.). If it stops passing, recent changes get backed out until your build works again.
It was only couple of years ago when I first heard about continuous integration - and was shocked. "Not everybody is doing this already? Duh." For me, having a continuously working build of the source has always been a natural approach, and working on Bugzilla (and with Mozilla project's autobuild tools such as Tinderbox and Bonsai) just strengthened the habit.
Even if you don't have an automated build process, get rid of the separate concept of "integration". Every time you check in to your repository, make sure your build works. With a small team, it works pretty well even without the automation as long as everybody is feeling responsible and checks in some code every day. And if you haven't read anything on continuous integration yet, go check out Martin's article. It's good, even though it contains little new for those already familiar with the concepts.
Some ranting on code reviews in general:
Bugzilla's bug 185090 is nearing completion, and I certainly hope I've finished my part by finally granting review to the 70 kbyte patch. To the no less than 13th iteration of the patch. For those of you not familiar with the Mozilla family review process, a short recap: Every change to the codebase must be reviewed by at least one designated code reviewer. The review is usually conducted by looking at the cvs diff ("patch"), pointing out issues and discarding the patch until its clean.
The patch I finally r+'d was far from the biggest in Bugzilla (or even my) history. Nor was 13 iterations an extreme amount if we compare to the general group of patches for major enhancements. Yet still, doing line-by-line review for 70 k of code several times in a row is hell on earth. It's always easy to come up with lots of comments for the first patch, but after several iterations most people (including me on most days) simply cannot focus enough to effectively review the code as a whole time after time again. You just become numb.
Reviewing interdiffs ("patches of patches") doesn't work very well for larger changes except when the interdiff is trivial. It's extremely easy to miss issues that way, and reviewing code readability in context is hard if not impossible. So in the end, the only way to effectively review is by looking at the patch, testing, and looking more at the patch.
Of course, there's no perfect solution. But having done quite a many fairly big (100+k) code reviews both for Bugzilla and my former employer, I'm pretty certain there are very few features that require such amount of new logic in one patch. So once again, the key is small iterative changes . Apart from find/replace changes such as renaming identifiers, almost every change can be split into a few easily reviewable patches (10-20 k is pretty nice).
But if so, why don't people split their patches more eagerly? For many development cultures, I think it's a matter of false beliefs about time. Getting a review from your collegue (at work) or someone from the dev group (for Bugzilla/Mozilla/etc. development) can take days, weeks at worst. "Well, in that case it's most effective to have them review as much as possible in a single run, right?" No.
Long review queues are, to a large extent, caused by the fact that a thorough review of a 100k patch takes at least a couple of hours. Since you can't allocate that big a time slice easily, you tend to slip on reviewing. But it's usually far easier to find time for half a dozen 30 minute sessions than to allocate a single 2-hour slot - and it's very much easier to go through half a dozen 20k patches than a single 100k one. Also, let me remind you that the amount of iterations required for a positive review rises very sharply in relation to the patch size.
To play some number games, assume that a reviewer will be able to go through five 20k patches or one 100k patch in a week. Assume that a feature can be implemented either in a single 100k giant or iteratively in seven 20k patches. And finally, assume that it takes 6 iterations for the 100k patch to be ready, while a 20k patch can be checked in after getting three rounds of review. Well, getting 6 reviews for the 100k patch takes 6 weeks. Getting 3 reviews for seven 20k patches each takes just a bit over four weeks (3x7/5)!
The example above is pretty conservative. In practice, I'd pick _ten_ 20k patches anytime instead of a single 100k lump. Also, the 3-6 balance in the iterations required is unrealistic: the difference is usually more drastic. But I guess this shows the point. Next time somebody asks for review on a big patch, my first aim is to find a reason I can deny review based on a "You can do this in smaller steps" type argument.
Ps. At this point, it's fair to admit I didn't come up with a decent way to split the patch that started all this ranting. But for many megabugs the answer is pretty apparent, and those are the ones that should get hacked into mincemeat.
When reviewing the code for a massive new feature for Bugzilla, a question on the proper way to write logic stepped up again. Do we use the ternary conditional operator ?: ? Do we use explicit boolean constants true and false instead of relying on expression results? Let me demonstrate the issues encountered with some equivalent snippets of code. Assume this is C#, but C++/Java/Perl etc. wouldn't differ much.
// Style 1: The most verbose
public bool isEvenInteger(int num) {
if (num % 2 == 0)
return true;
else
return false;
}
// Style 2: Use ?: instead of the if conditional
public bool isEvenInteger(int num) {
return (num % 2 == 0) ? true : false;
}
// Style 3: Discard ?:
public bool isEvenInteger(int num) {
return (num % 2 == 0);
// C++ posse would say just "!(num % 2)"
}
So the question: Which one of these is the most readable?
For the beginning programmer, one is tempted to think about style 1. After all, in that example, the only non-english things are the % (modulo) operator and the use of double ='s - but once you know them, it's pretty clear-cut. On the other hand, style 2 is much more compact. It uses the ternary conditional operator, where a ? b : c is roughly equivalent to if (a) b; else c;.
Since the a part - the condition of the ?: operator - is always a boolean expression, the ?: operator is useless when you want to return a boolean value. In fact, ? true : false can always be removed, and ? false : true can always be replaced with a not operator. And because of this, most programmers with some experience pick model 3, which is terse and compact.
And that marks the kickoff on the discussion of whether code is too tightly-packed (for readability) after all unnecessary parts are discarded. For the C++ form briefly noted in the example, I think it is. For the uncommented version of style 3, I'm inclined to say no. Here's some argumentation on the issue.
People often fear ?: because of its unnatural syntax. To some extent, this is also caused by misuse of the operator: f.e. construction of switch trees with it almost always results in unreadable code. Also, ?: is a prime candidate for obfuscating your logic. But that's not to say it couldn't be useful: terse syntax is not necessarily an enemy of readability. Take a look at the following typical code examples:
int age = AGE_NOT_GIVEN; if (userinput_age > 0) age = userinput_age; // vs. int age; if (userinput_age > 0) age = userinput_age; else age = AGE_NOT_GIVEN; // vs. int age = (userinput_age > 0) ? userinput_age : AGE_NOT_GIVEN;
Upon closer inspection, any programmer easily understands any of these structures. The first of them is probably the worst, as it forces the reader to both evaluate the first (default value) assignment, then think about the if condition and the other assignment inside the conditional block. The second one makes it much more clear that age is assigned either of the two values, so it beats the first one.
But both of the two first styles have one drawback: At a glance, it's not easy to tell that the age is assigned a value. The if blocks could contain whatever code, so you have to read through them to see that age value is actually getting assigned. With the third form, the first 10 characters tell you age is getting assigned: thereafter it's just a question of what's the value it gets assigned to.
When looking at the code from a more general perspective, you're not interested in the details; you're interested in the generic flow of things ("first age gets assigned, then WibbleMyToes(age) is called, after which the return value is saved"). The more pages you have to go through, the more time it takes. So, using compact syntax for logical trivialities avoids stealing focus from more important issues. And yes, this means there is a consequent rule: Do not try to pack logic that is critical to understanding the code flow. I believe most of the bad examples of using ?: stem from breaking that rule.
That said, it's also easy to see why you shouldn't write ? true : false: It is just another way to emphasize trivialities, just like unnecessary commenting in the spirit of i++; // Increment i by one.
Full circle is easy to achieve here: Beginning coders mostly (have to) focus on the code at the statement level. Advanced developers have already devised a skill to read code one page at a time. Let's return to the first set of examples. While the first style is probably the most readable form for beginners, it degrades the code browsability in the long run. The last one may take some time for beginners to read, but it is precise, to the point and exactly as unnoticeable as it should be.
There will always be somebody whining about using language features such as the ?: operator. That's understandable. People are bound to complain, because the tight syntactical structures are something they can point at. If you expand all the code, the big picture may still be unreachable for them (because of the length and depth in the code), but they're more likely to blame themselves as they can't spot a single culprit for their lack of understanding. It takes quite a lot of programming experience to come up with opinions like "You're commenting too much here" or "You should tighten this syntax".
Of course, no rule is a silver bullet. If your conditions get really long or if they're particularly critical for the software's functionality, use an if block - at least the structure of an if statement leaves you much more room for useful comments.
Also, everything said above shouldn't fool you into thinking there is a single correct way to do things. There isn't. These are all just factors in a bigger game of selecting a coding style and being consistent with it. That's one of the more interesting challenges in developing open source software - with a developer group of insanely varying backgrounds and competence.