Chapter 6. Spiking

Experimentation is a fundamental part of research. To understand and validate how something works, we experiment with it. Spiking is the XP term given to research and experimentation. Spiking provides a way to discover more information about a specific part of the project.

This chapter guides you through carrying out some spikes (research) on technologies that you have not encountered. After you have spiked an area of technology, you have a much better understanding of what it will take to develop software using that technology. This understanding will enable you to more effectively break down stories into tasks and make more accurate time estimates for those tasks.

You Can’t Know Everything

The vast amount of information available about even something as focused as just the .NET Framework means that it is nearly impossible to know everything about it. Most of us don’t just concentrate on one area of work. XP development environments do much to discourage specialization.

If you specialize in a particular field, you are potentially damaging both your career and the business you are working with. The specialization takes your focus away from seeing the bigger picture, which is harmful to both you and your business. There is no point in knowing everything about how to optimize databases if the core business needs of the software are not being met. If the data is stored in a way that does not provide easy access to valuable information for people in the company, it matters not how well its storage is optimized.

What is important is knowing how to get that information when you need it. Spiking is the tool that will help you get that information when you need it.

Raise Your Confidence

By having a rough understanding of how a technology works, you can boost your confidence in that area by doing some experimentation. You don’t want to build the entire system or develop a fully working prototype. It is more important to drive a thin vertical wedge of understanding through the area of concern. This is where the term spike comes from; it is a thin vertical slice of information.

When you are experimenting, focus on understanding one specific thing, not a whole host of things. That way you can focus on solving one problem and not become distracted. Set a goal that is achievable in a relatively short time period. If you are trying to understand a complex technology, use your task breakdown skills. Break down the problem into a collection of smaller problems. As mentioned in Chapter 3, I like to aim for something that I believe is achievable within a four-hour timeframe. If a problem takes longer than this to solve, I realize I have bitten off more than I can chew. I need to take a step back and break down the problem.

In each spiking session, I attempt to build a prototype that I will be happy to throw away. The point is not to build a piece of software from these sessions, but to gain an understanding that I can apply to the software system being developed.

Let’s Discover Something

Let’s rejoin our eXtreme .NET team to see how they are learning about spiking.

Exercise 6-1: Spiking How Time Zone Data Works in Windows

In this exercise, we are going to investigate how we can use time zone information in our application. In Chapter 3 we encountered this as a task we couldn’t accurately estimate. We didn’t know enough about how time zones are stored to give an accurate estimate. We need to understand how to access the time zone data from the operating system. Then we need to work out how to use this information to get the time in different places in the world. With this knowledge, we can go back to our team with some firmer estimates about how long this task will take to develop.

The first place to look is the classes that ship as part of the .NET Framework. If there is support for time zone data there, our lives will be made much simpler.

  1. Open up the help file and look up the .NET Framework DateTime structure.

    Unfortunately the only time zone–related functionality provided is to convert local times to time in UTC (formally GMT; don’t ask me why this was changed!) and back again. This just works using the local time zone that the machine is currently running in. As the documentation states:

    This method assumes that the current DateTime holds the local time value, and not a UTC time. Therefore, each time it is run, the current method performs the necessary modifications on the DateTime to derive the UTC time, whether the current DateTime holds the local time or not.

    This method always uses the local time zone when making calculations. (Source: MSDN library)

  2. Within the System namespace, there is a TimeZone class; let’s see whether that is useful for us.

    All the documentation has to say about it is this:

    A time zone is a geographical region in which the same standard time is used. (Source: MSDN library)

    Not very verbose, but it sounds as though it might be promising.

    If you examine the methods, you will discover that the constructor is protected, and the only way to create an instance of this class is to use the static property CurrentTimeZone. This is not very useful if we want to get the time zone information for different time zones.

    (As another exercise, you can try to build a test application using the Time-Zone type and see whether you can get time zone for some other part of the world.)

  3. Our next stop is to see what the Win32 API provides in terms of time zone functionality. A couple of functions look interesting: GetTimeZoneInformation and SetTimeZoneInformation. They both work with a TIME_ZONE_INFORMATION structure, which contains all the data that we need to build our application; for a given time zone, the fields in the structure are as shown in the following table.

    Time Zone Fields

    Field

    Description

    Bias

    The offset from UTC in minutes

    Standard Name

    The name of standard time on this machine in this time zone

    Standard Date

    The date and time that daylight savings moves over to standard time

    Standard Bias

    The additional difference from UTC during standard time (usually zero)

    DaylightName

    The name of daylight savings time on this machine in this time zone

    DaylightDate

    The date and time of the transition from standard time to daylight savings time

    DaylightBias

    The additional difference from UTC during daylight savings time (usually–60)

    This looks promising, but the methods only enable you to get or set the time zone on the local machine and retrieve time zone information about the time zone set on the local machine.

    We are still stumped. We want to get time zone information for all the available time zones in the world. We figure it must be possible because the Control Panel extension that you use to set the Date and Time does it in the Time Zone tab.

  4. That is the next place for us to look. If we could work out how the timedate.cpl (basically a DLL) worked, we could emulate that and get all the time zone information for around the world. So, first, we can look to see whether it exposes any form of interface (COM or .NET). You can try to open it using OLE View or ildasm, but no luck there; it supports neither a COM interface nor does it contain CLR header information.

  5. Back to basics, open the CPL in Visual Studio as a resource to examine whether there are any clues in there. Maybe the time zone information might have been stored in the DLL (maybe in a string table).

    Nope.

    The only thing left we can think of is to open up the timedate.cpl file in a hex editor. (Yuck, I haven’t poked around inside Windows system files for a while; surely there’s a good reason for that!)

    We begin this stage of the investigation by examining the DLLs that the code uses, all the usual suspects: KERNEL32, NTDLL.DLL, USER32.dll, COMCTL32.dll, ole32.dll, SHELL32.dll, GDI32.dll, ADVAPI32.dll, IMM32.dll and SHLWAPI.dll. Nothing of use here.

    So carry on looking through the file for any other obvious clues; maybe the time zone data is hard coded in the DLL. Again no.

    So where did the time zone information get stored? How about the Registry? Look for any references to Registry paths in the CPL file; you should find a few. The first ones lead to information about time synchronization via a timeserver and then some Registry entries for storing the current time zone settings for the local machine. Then about halfway down the file you can find a reference to the Registry key: SoftwareMicrosoftWindows NTCurrentVersionTime Zones.

    Eureka!

  6. This key contains a subkey for each time zone that is listed in the combo box in the second tab of the timedate.cpl. The data in each of the subkeys has to provide all the data required to build a TIME_ZONE_INFORMATION structure so that the application can set the time zone for the system. Now we just have work out how the data is formatted.

    Registry Information for Each Time Zone

    Value

    Meaning

    Display

    Extra information about the time zone for display purposes

    Dlt

    DaylightName; the name of daylight savings time on this machine

    Index

    Unknown

    MapID

    Presumably used for placing on the bitmap of the world in the CPL

    Std

    StandardName; the name of standard time on this machine

    TZI

    All the other data we need to fill a TIME_ZONE_INFORMATION structure

  7. The string data is reasonably clear, as shown in the preceding table; we just need to work out how the byte data in the TZI (Time Zone Information) is stored. We start off by going back to the documentation on the structure, from which we can draw up the following table. So adding up the numbers, we are looking for 44 bytes of information, and the TZI value in the Registry is exactly 44 bytes long. Therefore, we must assume that we have all the data we need in the byte array. All we need to do is figure out which byte means what!

    Break Down of Time Zone Information

    Data Type

    Name

    Description

    Bytes

    LONG

    Bias

    In minutes

    4

    SYSTEMTIME

    StandardDate

    Month, day of week and day only

    16

    LONG

    StandardBias

    Mostly 0 (minutes)

    4

    SYSTEMTIME

    DaylightDate

    Month, day of week and day only

    16

    LONG

    DaylightBias

    Mostly –60 (minutes)

    4

  8. On your own, write a program that decodes the byte data in the TZI fields in the Registry, and try to work out how the time zone information data is stored in the TZI field. The best thing to do would be to write some tests that validate how we believe the time zone data is stored. We can run those tests when the OS changes and check whether the way time zone data is stored has changed.

    You should have deduced the data was stored in the following order: Bias, Standard Bias, Daylight Bias, Standard Date, and Daylight Date. Okay, now we are ready to put together some code to use this information.

    Below are some functions written in C# that use the time zone information from the Registry.

  9. To populate a list with the available time zones, a static method on a class is shown first. It is static because it requires no instance data to be functional.

    public static string[] GetTimeZones()
    {
        RegistryKey regKey = Registry.LocalMachine;
        regKey = regKey.OpenSubKey(
            @"SOFTWAREMicrosoftWindows NTCurrentVersionTime Zones");
        return regKey.GetSubKeyNames();
    }
    

GetTimeZones Function

After a time zone has been selected, you must get the time zone information for the place selected. The next set of code demonstrates this.

  1. Open the Registry key for the time zone provided.

  2. Read in the TZI value to a byte array.

  3. Use some helper functions to extract the data out of the byte array and into the member variables of the PlaceTime object.

The helper functions MakeUShort and MakeInt are equivalent to the traditional Windows MAKEWORD and MAKELONG macros. Remember that a Long in .NET world is 64 bits and not 32 bits as it was in the old Win32 world. GetValueFromBytes builds a 32-bit integer value from an array of bytes, using an offset from the beginning of the array at which to start “stripping” the bytes.

protected void GetTimeZoneInfo(string strTimeZone)
{
    RegistryKey regKey = Registry.LocalMachine;
    regKey = regKey.OpenSubKey(
        @"SOFTWAREMicrosoftWindows NTCurrentVersionTime Zones" +
            strTimeZone);
    System.Byte[] tziData = (Byte[])regKey.GetValue("TZI");

    m_nBias = GetValueFromBytes(tziData, 0);
    m_nStandardBias = GetValueFromBytes(tziData, 4);
    m_nDaylightBias = GetValueFromBytes(tziData, 8);

    m_StandardDate = GetDateTimeFromBytes(tziData, 12);
    m_DaylightDate = GetDateTimeFromBytes(tziData, 28);
}


private static ushort MakeUShort(byte low, byte high)
{
    return (ushort) (low | (high<< 8));
}

private static int MakeInt(ushort low, ushort high)
{
    return (int) (low | (high<< 16));
}

private static int GetValueFromBytes(byte[] data, int offset)
{
    int nResult = 0;
    ushort lowWord = MakeUShort(data[offset],data[offset+1]);
    ushort highWord = MakeUShort(data[offset+2],data[offset+3]);
    nResult = MakeInt( lowWord, highWord );
    return nResult;
}

private static DateTime GetDateTimeFromBytes(byte[] data, int offset)
{

    DateTime dateTime = DateTime.UtcNow;

    int Year = MakeUShort(data[offset],data[offset+1]);
    int Month = MakeUShort(data[offset+2],data[offset+3]);
    DayOfWeek DayofWeek =
         (DayOfWeek)MakeUShort(data[offset+4],data[offset+5]);
    int Day = MakeUShort(data[offset+6],data[offset+7]);
    int Hour = MakeUShort(data[offset+8],data[offset+9]);
    int Minute = MakeUShort(data[offset+10],data[offset+11]);
    int Second = MakeUShort(data[offset+12],data[offset+13]);
    int Milliseconds = MakeUShort(data[offset+14],data[offset+15]);

    dateTime = dateTime.AddMonths(Month - dateTime.Month);
    dateTime = dateTime.AddDays(1-dateTime.Day);
    dateTime = dateTime.AddHours(Hour - dateTime.Hour);
    dateTime = dateTime.AddMinutes(Minute - dateTime.Minute);
    dateTime = dateTime.AddSeconds(Second - dateTime.Second );
    dateTime = dateTime.AddMilliseconds(Milliseconds –
        dateTime.Millisecond );
    bool bFoundDay = false;
    int nCoveredRequiredDay = 0;
    while (dateTime.Month == Month)
    {
        if (dateTime.DayOfWeek == DayofWeek)
        {
            nCoveredRequiredDay +=1;
            if (nCoveredRequiredDay == Day)
            {
                bFoundDay = true;
                break;
            }
        }
        dateTime = dateTime.AddDays(1);
    }
    while(!bFoundDay)
    {
        dateTime = dateTime.AddDays(-1);
        if (dateTime.DayOfWeek == DayofWeek)
        {
            bFoundDay = true;
        }
    }
    return dateTime;
}

GetTimeZoneInfo and Related Functions

The final helper function, GetDateTimeFromBytes, builds a .NET Framework Date-Time structure from the byte array passed in, starting at the offset provided. This is the more complicated of the methods here and requires some extra explanation.

The TZI byte array contains 2 sets of 16 bytes, which correspond to a Win32 SYSTEMTIME structure. These SYSTEMTIMEs contain the data for the Standard Date and Daylight Date in the TIME_ZONE_INFORMATION structure. The standard and daylight dates represent the date and time at which the transition occurs from daylight savings to standard time and back again.

The TIME_ZONE_INFORMATION structures contain the month, the day of the week (for example, Sunday), the day on which the transition occurs, along with the time. The day can be a value between 1 and 5. If the month is 4, the day is 1, and the day of the week is 0 (Sunday), that would represent the first Sunday in April. If the day value is 5, that always means the last of that day in the month. If the month is 9, the day is 5, and the day of the week is 6, which represents the last Saturday in September.

The GetDateTimeFromBytes function sets the DateTime structure to the first day of the month given and walks through the month until it finds the correct day of the transition. If the method fails to do this, it assumes (dangerous, I know) that we want the last occurrence of that day of the week in the month and starts walking backward through the month until it finds that day. I have no doubt this could be greatly optimized, but I leave that as another exercise for you!

Encode the Knowledge in Tests

As previously mentioned, putting your newly found knowledge into tests is a great way to capture the knowledge you have gained. The tests not only document your findings for other developers to read, they also ensure the knowledge is still valid and not out-of-date. It is sometimes hard to write the tests when you do not know what you are testing, so spiking is often carried out without tests being written first. This is not always the case, as Ron Jeffries shows us in his book Extreme Programming Adventures in C# (Microsoft Press, 2004). In the book, Ron carries out spikes purely with tests.

It is not a hard-and-fast rule, and I personally find I can be more playful and creative if I can muck around with ideas in code without the concern of testing. I can write the tests after I understand the technology being researched.

Let’s put our newly gained knowledge of how the time zones are stored into a test fixture. Notice that we are testing the helper methods that build the values out of the byte arrays. These helper methods will likely be extracted into a real class when we come to use the time zone information in our project. Having tests for them means that the refactoring exercise that will happen when we need those methods can be done with much more confidence.

using System;
using Microsoft.Win32;
using NUnit.Framework;

[TestFixture]
public class TimeZoneTests
{
    [Test]
    public void TestRegistryKey()
    {
        RegistryKey regKey = Registry.LocalMachine;
        regKey = regKey.OpenSubKey(@"SOFTWAREMicrosoftWindows NTCurrentVer-
sionTime Zones");
        Assert.IsNotNull(regKey, "Invalid registry key");
    }

    [Test]
    public void TestMakeUShort()
    {
        ushort testVal = MakeUShort(0, 0);
        Assert.AreEqual(0,testVal);

        testVal = MakeUShort(1, 0);
        Assert.AreEqual(1,testVal);

        testVal = MakeUShort(byte.MaxValue, 0);
        Assert.AreEqual(byte.MaxValue,testVal);

        testVal = MakeUShort(0, 1);
        Assert.AreEqual(byte.MaxValue+1,testVal);

        testVal = MakeUShort(byte.MaxValue, byte.MaxValue);
        Assert.AreEqual(ushort.MaxValue,testVal);
    }

    [Test]
    public void TestMakeInt()
    {
        int testVal = MakeInt(0, 0);
        Assert.AreEqual(0,testVal);

        testVal = MakeInt(1, 0);
        Assert.AreEqual(1,testVal);

        testVal = MakeInt(ushort.MaxValue, 0);
        Assert.AreEqual(ushort.MaxValue,testVal);

        testVal = MakeInt(0, 1);
        Assert.AreEqual(ushort.MaxValue+1,testVal);

        testVal = MakeInt(ushort.MaxValue, ushort.MaxValue);
        Assert.AreEqual(-1,testVal);
    }

    [Test]
    public void TestGetValueFromBytes()
    {
        byte[] testBytes = {0,0,0,0,0,0,0,0};
        int testVal = GetValueFromBytes(testBytes,0);
        Assert.AreEqual(0, testVal);

        testVal = GetValueFromBytes(testBytes,4);
        Assert.AreEqual(0, testVal);
        testBytes[0] = byte.MaxValue;
        testBytes[1] = byte.MaxValue;

        testVal = GetValueFromBytes(testBytes,0);
        Assert.AreEqual(ushort.MaxValue, testVal);

        testBytes[2] = byte.MaxValue;
        testBytes[3] = byte.MaxValue;

        testVal = GetValueFromBytes(testBytes,0);
        Assert.AreEqual(-1, testVal);

        testBytes[0] = 0;
        testBytes[1] = 0;

        testVal = GetValueFromBytes(testBytes,0);
        Assert.AreEqual(-1-ushort.MaxValue, testVal);
    }

    private ushort MakeUShort(byte low, byte high)
    {
        return (ushort) (low | (high<< 8));
    }

    private int MakeInt(ushort low, ushort high)
    {
        return (int) (low | (high<< 16));
    }

    private int GetValueFromBytes(byte[] data, int offset)
    {
        int nResult = 0;

        ushort lowWord = MakeUShort(data[offset],data[offset+1]);
        ushort highWord = MakeUShort(data[offset+2],data[offset+3]);
        nResult = MakeInt( lowWord, highWord );

        return nResult;
    }

    [Test]
    public void TestTimeZoneInfoForGMT()
    {
        RegistryKey regKey = Registry.LocalMachine;
        regKey = regKey.OpenSubKey(
           @"SOFTWAREMicrosoftWindows NTCurrentVersionTime ZonesGMT Stan-
dard Time");
        Assert.IsNotNull(regKey, "Invalid registry key");
        System.Byte[] tziData = (Byte[])regKey.GetValue("TZI");

        int bias = GetValueFromBytes(tziData, 0);
        Assert.AreEqual(0, bias, "Bias was incorrect");
        int standardBias = GetValueFromBytes(tziData, 4);
        Assert.AreEqual(0, standardBias,
            "Standard Bias was incorrect");
        int daylightBias = GetValueFromBytes(tziData, 8);
        Assert.AreEqual(-60, daylightBias,
            "Daylight Bias was incorrect");

        int month = MakeUShort(tziData[14],tziData[15]);
        Assert.AreEqual(10, month,
            "Month for change to standard time should be Oct");
        DayOfWeek dayofWeek = (DayOfWeek)
            MakeUShort(tziData[16],tziData[17]);
        Assert.AreEqual(DayOfWeek.Sunday, dayofWeek,
            "Time should change on a Sun");
        int day = MakeUShort(tziData[18],tziData[19]);
        Assert.AreEqual(5, day,
            "Time should change on 5th or last Sunday of month");
        int hour = MakeUShort(tziData[20],tziData[21]);
        Assert.AreEqual(2, hour,
            "Time should change at 2am");
        int minute= MakeUShort(tziData[22],tziData[23]);
        Assert.AreEqual(0, minute,
            "Time should change at 2am - zero minutes");

        month = MakeUShort(tziData[30],tziData[31]);
        Assert.AreEqual(3, month,
            "Month for change to daylight savings should be Mar");
        dayofWeek = (DayOfWeek)
            MakeUShort(tziData[32],tziData[33]);
        Assert.AreEqual(DayOfWeek.Sunday, dayofWeek,
            "Time should change to daylight on a Sun");
        day = MakeUShort(tziData[34],tziData[35]);
        Assert.AreEqual(5, day,
            "Time should change on 5th or last Sunday of month");
        hour = MakeUShort(tziData[36],tziData[37]);
        Assert.AreEqual(1, hour,
            "Time should change at 1am");
        minute= MakeUShort(tziData[38],tziData[39]);
        Assert.AreEqual(0, minute,
            "Time should change at 1am - zero minutes");
    }
}

Go Where No Man Has Gone Before

Spiking is a skill, one that must be learned. And one thing you must learn is to relax and open your mind to the different possible solutions to your problem. Often, obvious solutions will not solve your problem; you have to look deeper. That’s why we get paid as software developers to solve such problems!

The following exercises reinforce what you have learned in this chapter and require that you consider spiking in a bit more detail. As you work through the exercises, remember to encode your learned knowledge into tests. If you get stuck at any point, look for some clues on my Web site at http://eXtreme.NET.Roodyn.com.

Exercise 6-2: Spike Web Services Without a Web Server

The client wants to build an application that can be run on many machines in his company (laptops, desktops, and servers). He wants each machine to provide some status information via a Web service that can be queried on that particular machine. The machines might not necessarily be running IIS. The question is whether can we build a .NET Web service without the need for that machine to host a Web server.

Exercise 6-3: Spike Session State Across Service Calls

Our customer wants to amalgamate a number of Web service functions through a Web site. We propose to build the Web site in ASP.NET. The issue is that some of the Web services need to maintain state. Is it possible to do this?

Exercise 6-4: Spike Drag and Drop Documents in a Rich Text Control

The application we are building requires that it interact with other documents by allowing the user to drag a document from Explorer and drop it onto a Rich Text box. As with Outlook when you send an e-mail, the user wants to see the document as an icon and not the contents of the document. Is this something we can easily achieve?

Conclusion

Spiking is about experimentation and about being prepared to throw away the code you have written while retaining the knowledge gained from it. It is a fundamental skill that many developers start with but lose when under pressure to deliver business-value functionality under strict deadlines. The discipline to take a step back and experiment in a structured way will prove very valuable in the long run, enabling you to better understand new technologies and validate theories about various systems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.93.12