2008-02-15

The art of writing a good bugreport

if at first you don't succeed... file a bugreport

Over the years I've seen quite a number of bug reports, and often they were inspired by a bug, and often enough, the bug was mine to fix.

In order to fix a bug, a developer needs a good bug report. The classic case of a bad bug report that we have used for a long time now in the eco team is the "kaboom" bug report.
The customer simply said: "I tried to do this and that and 'kaboom'!"... how were we supposed to interpret that? did the machine really explode?

Here are a few things to think about when you report a bug to someone else:

Reproducible:

Make sure you can reproduce the bug. Make sure you know the required starting point and the exact steps to make the bug appear. If you don't know these things, at least be honest about it, and preferably, find a good discussion forum to ventilate your problem and see if anyone else knows the steps to reproduce the issue before reporting it.
In some cases, it could be enough with just an exception message and a callstack to be able to fix the bug, but it is hard to know if the bug is really fixed if it can't be reproduced.

Steps:
If your bug is reproducible, then you know the steps to reproduce it. The developer responsible for fixing the bug hasn't seen the bug happen when he reads the report. He (or she) will need detailed steps in order to reproduce the bug.
Ideally, the steps looks something like
* New winform eco project
* add a class with an attribute of type X
* generate source
* Drop a button on the form, execute:
Exp: this
Act: that

Exp and Act
It is very important to explain both what actually happens (act9, and what you expected to happen (exp). Sometimes, maybe your expectation is wrong, and what really needs fixing is the documentation so that you know what to expect. In order for the developer to know that he has reproduced your bug he needs to know what happens in your environment.

Isolate the bug:

The fewer steps needed to expose the bug, the better. Try to eliminate as much noise as possible. If you are using third party components, try to reproduce the bug without those. If the bug doesn't seem to be related to persistence, try to reproduce the issue in a transient system.

Sample project:
In many cases, a small example project that exhibits the bug can be helpful, but sending your 100kloc project with a 3gb sqlserver database is usually not appreciated. Also, when you attach a testcase to your bugreport, please remove any compiled files unless you are certain they are needed to reproduce the bug. Zip up your project and then remove the following:
*.exe; *.dll; *.pdb; *.dc?il; *.identcache; bin-dir, obj-dir, any history/backup files.

Exceptions and Callstacks:
Callstacks are often very helpful for a developer to figure out what is wrong. The exact error message in the exception is also very important. Please include these if you get an exception.

Unit tests:
I have been thinking of writing this piece for many years, and I had always planned to add a contest at the end. The first person who would submit a previously unknown bug with an accompanying unittest that exposed the bug would win a beer. I had never seen this in any of the bug reports I have received in the past 10 years as a developer until Dmitriy Nagirnyak sent me a bugreport last october (http://magpie.sytes.net/mantis/view.php?id=237). Read and learn folks! It was a fairly complicated bug, but Dmitriy had done his homework and isolated the bug, prepared a testcase and explained all the details required to understand the bug. Instead of promising a beer to the winner (Dmitriy, whenever we meet the beer is on me), I promise a private build off the current stable branch of eco if you submit a new bug with a relevant isolated nunit-testcase as soon as the fix has been checked in.

So... if you find a bug, don't be afraid to report it, but please follow these guidelines when you do.

2008-01-31

Dynamic instantiation of generic types

If at first you don't succeed...Change the rules.

Once I had managed to create the implementation of IList[T] (see last post), I was faced with the problem of instantiating it dynamically. All I had was the type and a reference to the type that I wanted to substitute for T. The argument to the generic type was not fixed statically.

I tried to get System.Type.GetType(string s) to return the type:

System.Type.GetType(
'MyNameSpace.MyGenericType`1[MyOtherNameSpace.MyOtherType]');


but it simply didn't want to return my type. I finally settled for this:


Type arg = (* expression that resulted in a type reference *)
object temp = new MyGenericType[object]();
Type t = temp.GetType.
GetGenericTypeDefinition.MakeGenericType(new Type[]{arg});
object newObj = Activator.CreateInstance(
t,
new object[]{ (* constructor parameters *)} );

Anyone know a way around the Activator class to improve performance?

Implementing generic IList in Delphi.NET

If at first you don't succeed... hide your astonishment

Last night I had to implement the generic version of IList in delphi. It turned out to be a little more tricky than I had hoped... The compiler didn't always give the most helpful error messages. Anyway, here is a minimal implementation of System.Collections.Generic.IList[T] in Delphi.NET (including a custom enumerator). Just replace all square brackets with the angle brackets (couldn't figure out how to get blogger to show them).

type
TMyListImpl[T] = class(TObject, IList,
ICollection, ICollection[T], IList[T])
function IList.GetEnumerator = IList_GetEnumerator;
function IList.get_Item = IList_get_Item;
public
function IList_GetEnumerator: IEnumerator;
function GetEnumerator: IEnumerator[T];
procedure CopyTo(arr: System.Array; index: integer); overload;
procedure CopyTo(arr: array of T; index: integer); overload;
function get_SyncRoot: System.Object;
function get_IsSynchronized: Boolean;
function get_Count: integer;
procedure Add(value: T); overload;
function Add(value: TObject): integer; overload;
function ICollection_Add(value: TObject): integer;
procedure Clear;
function Contains(value: T): boolean; overload;
function Contains(value: TObject): boolean; overload;
function IndexOf(value: T): integer; overload;
function IndexOf(value: TObject): integer; overload;
procedure Insert(index: integer; value: T); overload;
procedure Insert(index: integer; value: TObject); overload;
function Remove(value: T): Boolean; overload;
procedure Remove(value: TObject); overload;
procedure RemoveAt(index: integer);
function get_IsFixedSize: Boolean;
function get_IsReadOnly: Boolean;
function get_Item(index: integer): T; overload;
procedure set_Item(index: integer; Value: T); overload;
function IList_get_Item(index: integer): TObject; overload;
procedure set_Item(index: integer; Value: TObject); overload;
property Item[index: integer]: T read get_Item write set_Item;
property IsReadOnly: Boolean read get_IsReadOnly;
property IsFixedSize: Boolean read get_IsFixedSize;
property Count: integer read get_Count;
property SyncRoot: System.Object read get_SyncRoot;
property IsSynchronized: Boolean read get_IsSynchronized;
end;

TListEnumerator[T] = class(System.Object,
IEnumerator, IEnumerator[T])
function IEnumerator.get_Current = IEnumerator_get_Current;
strict private
fList: TMyListImpl[T];
FCurrentIndex: integer;
public
constructor Create(list: TMyListImpl[T]);
function get_Current: T;
function IEnumerator_get_Current: TObject;
function MoveNext: Boolean;
procedure Dispose;
procedure Reset;
end;