Telstra’s Complaint Process (Part 2)

February 17th, 2009

When I received an unsatisfactory reply to my first complaint to Telstra I started work on a new scathing reply hoping to get the response I wanted from my first complaint. Unfortunately Telstra’s reply was almost identical.

Hi,

I have attached my original complaint letter in case it has been misplaced.

Thank you for your reply. When I first read it I laughed. Despite the amount of practice you (I’m using the term to collectively refer to Telstra (whom you represent), so please do not take this as a person attack) must have dealing with complaints you don’t seem particularly good at it.

I refer to the first line of my email. That’s right, the first line: “Please redirect this complaint to the necessary area”. Nowhere in my email did I suggest that it would be even a remotely acceptable response to provide telephone numbers where I could presumably read my letter to. In fact, one of these phone numbers was the subject of my fifth complaint (see original letter attached).

Ignoring the first line of my letter is much like “showing a red rag to a bull” or “poking the bear”. More likely these terms are referred to internally as “servicing the customer”.

I chose to send my complaint in writing for the primary reason that it could be forwarded to the appropriate people without losing anything in the translation. This is highly preferable to calling up, telling the whole story only to be passed on to another operator to start all over again.

Perhaps you don’t appreciate the wonders of the written word and its impact on history. Before cavemen started drawing images on the cave wall the only way to pass knowledge was through speech. Once written words were formed there became a means to pass on information without requiring the original author present. Furthermore the communication was able to be passed on exactly as the author intended.

This system was still held back by the amount of time it took to reproduce a written document. Fortunately the invention of the printing press made rapid duplication of a written document feasible leading eventually to increased literacy in the general populace. Several years later, computers were created that could copy information perfectly at high rates. This is where we are today.

I expected that it would be a simple case of locating an email address for the necessary departments and forwarding the email to them. Clearly this must be a new technology that hasn’t yet filtered down to Telstra from the world of academia.

Consequently I hope that Telstra is more familiar with the postal service. Please provide me with the postal address details of each of the relevant heads of department that my letter should be addressed to. Also, please provide the postal details for the head of Bigpond and head of Telstra who I will also send a copy of my letter to. I will be adding an additional covering letter detailing my dissatisfaction of your complaints handling process.

If you can not provide me with this information I would like a full explanation of why this is the case. “We do not provide this information” is not an explanation, nor is “call this number”.

Thank you for your time and I look forward to your prompt reply,

Rhys Parry

LINQ and Extension methods

February 16th, 2009

Have you ever wished that a base class had a particular method? What about interfaces? Wouldn’t it be great to define a method on an interface along with its implementation? Any class that then implemented the interface would get this implementation for free.

In the past this was achieved with static utility classes. Unfortunately this leads to cluttering your code with the names of these utility classes and dilute the expressiveness of your code. Let’s say we have a utility class the gets the words and word count from a string. Don’t worry too much about the implementation, just the general structure.

public static class StringUtilities
{
   private static readonly Regex wordsRegex = new Regex(@"\w+");

   public static IEnumerable<string> GetWords(string source)
   {
      return from word in wordsRegex.Matches(source).Cast<Match>()
             select word.Value;
   }

   public static int WordCount(string source)
   {
      return GetWords(source).Count();
   }
}

To use this in our code we would have to do something like this:

var sentence = "The quick brown fox jumps over the lazy dog";

// Display each of the words
foreach (var word in StringUtilities.GetWords(sentence))
{
   Console.WriteLine(word);
}

// Display the word count
Console.Write("Total Words: ")
Console.WriteLine(StringUtilities.WordCount(sentence));

Look at all that clutter. The truth in this context is that we are really performing an action on the sentence. Wouldn’t it be better if we could just call sentence.GetWords() or sentence.WordCount() instead? It would certainly be more readable. Extension methods make this all possible. Here’s our updated StringUtilities class that creates the extension methods:

public static class StringUtilities
{
   private static readonly Regex wordsRegex = new Regex(@"\w+");

   public static IEnumerable<string> GetWords(this string source)
   {
      return from word in wordsRegex.Matches(source).Cast<Match>()
             select word.Value;
   }

   public static int WordCount(this string source)
   {
      return GetWords(source).Count();
   }
}

We’ve added this before the variable type. The rest of the code has been left untouched. So now we can use the extension methods like so:

var sentence = "The quick brown fox jumps over the lazy dog";

// Display each of the words
foreach (var word in sentence.GetWords())
{
   Console.WriteLine(word);
}

// Display the word count
Console.WriteLine("Total Words: {0}", sentence.WordCount());

Doesn’t that read better? We have been able to push the implementation details (the name of the static utility class) out of our code.

How to enable an extension method

In order to use an extension method it must be part of the local namespace or imported with a using statement. Once that’s done you can call extension methods just as you would any normal method.

What does this have to do with LINQ?

LINQ is all about extension methods. When you import the System.Linq namespace it comes with a whole bundle of extension methods. Most of them act on IEnumerable<T> and can be used to write your LINQ queries in method syntax. Let’s look at this query:

from item in items
where item.Price < 1
select item.Name

This query finds the items that are under one dollar and returns their names. We can write this query in method syntax like so:

items.Where(item => item.Price < 1).Select(item => item.Name)

It’s not quite as readable (although that is a matter of opinion), but it gives a good indication of what is going on (and further demonstrates why select is at the end). These methods also take advantage of Lambda expressions (which I’ll discuss in a future post).

There are other useful extension functions that work with queries. Some of the ones you’ll use most often are:

  • ToList() executes the query and returns the results in a list. You will probably use this method a lot. I’ll cover this method an its consequences in more depth in a future post on deferred execution.
  • Count() executes the query and returns the number of results. When used with LINQ to SQL it will execute SQL code to get the database server to return the count.
  • Any() returns true if there are any results in the query. Use this instead of Count() > 0 to abstract out the implementation detail.
  • First() returns the first result from the query. This is particularly useful when you have a query that will only return one result (such as looking up an entry based on its primary key). This method will throw an exception (InvalidOperationException) if the query yields no results.
  • FirstOrDefault() returns the first result from the query, much like First(). If there are no results it will return the default for the type (e.g. 0 for an int, null for reference types).

Fortunately you aren’t limited to using these extension methods on LINQ queries. They are designed to work on any class that implements IEnumerable<T>. This means you can use them directly on a lot of the classes already in the .NET base class library.

What about old non-generic IEnumerable?

There are a lot of classes in the .NET framework that don’t implement IEnumerable<T> but instead implement the non-generic interface IEnumerable. A perfect example is MatchCollection used by Regular expressions. When we enumerate over a MatchCollection we are given the base object which we then need to cast to a Match object. Until we do this cast we can’t access any of the properties of Match. Fortunately there are a couple of LINQ extension methods designed to help out when dealing with IEnumerable.

  • Cast<T>() returns a strongly typed IEnumerable<T> object. Each object is cast to the type T. If an object can’t be cast an exception is thrown (InvalidCastException). In the case of a MatchCollection I am confident that every object is a Match object and an exception won’t be thrown.
  • OfType<T>() also returns a strongly typed IEnumerable<T> object. It goes further than Cast<T>() by only including objects of that type in the enumeration. In other words it filters out any class that isn’t of the desired type (without throwing exceptions). This is the method to use when you are unsure of what the type will be or if you are dealing with an enumeration that contains different typed objects.

If you want to see OfType<T>() in action, copy and paste the following example into LINQPad. (You’ll need to select C# Statement(s) from the language drop down).

var items = new object[]{"a string", 22, Math.PI};

items.OfType<string>().Dump("OfType<string>");
items.OfType<int>().Dump("OfType<int>");
items.OfType<double>().Dump("OfType<double>");

LINQPad has its own extension method Dump() which is used to output results to the LINQPad window. You’ll see that each individual dump returns a strongly typed IEnumerable<T> object. In this example items actually implemented IEnumerable<object>. Fortunately these methods don’t discriminate and happily work their magic on any IEnumerable<T> as well.

Still more to come

There is still plenty of more that I will post about LINQ. In my next post I’ll look at deferred execution, what it means and how you can take advantage of it.

Do automated tests need unit tests?

February 15th, 2009

This is an interesting question that I’ve been pondering over recently. My initial opinion was that if you have some sort of complex logic you should create unit tests to verify the code is sound. I still feel that complex logic in any code warrants unit testing, but I am beginning to wonder whether the need for unit tests may indicate a larger problem: your automation may be too complex.

But the complexity gets even more complex than that. Large suites of integrated tests are quite capable of getting complicated without your help. More abstraction and code reuse certainly makes things easier, but as you add more code paths you increase the likelihood of creating new bugs. Perhaps subtle bugs like using > when you should use >=. Maybe a regular expression isn’t quite right.

Some of these bugs may result in a “false fail” when the test is executed. At this point you need to begin failure analysis tracing the issue which could be either in your tests or in the application under test. Constant failures due to bugs in the test suite erodes confidence in the tests and I have seen failure analysis put off because it is believed to be an issue with the tests.

If you can’t trust your tests, who can you trust?

If you can’t be confident that a failure in a test represents a failure in the product under test your tests lose value. But what if your test passes when it should fail? In this case your test has no value as it has failed its primary purpose, to accurately confirm the application conforms to the test.

A passing test stays off the radar. Its functionality is assumed to work so may be overlooked during manual testing. Eventually the problem may be found, but possibly further down the line than is desirable such as during UAT or worse yet, in production.

By creating unit tests around our more complicated code we can improve our confidence in our own tests. It also sets a good example for the developers working on the application. We can’t expect them to write unit tests for their code if we don’t do the same.

We don’t need to have a unit test around every test (where would it stop?) but we certainly should be looking to at least verify our more complex bits of code.

Getting Started with LINQ

February 14th, 2009

I really like LINQ. It’s one of my favourite .NET features. When I first heard about it I was doing most of my programming in Visual Basic 6 (or worse, Visual Basic for Applications). Working now with C# and the .NET Framework has blessed me with full O-O, strong types, an excellent base class library, Visual Studio 2008 (and IntelliSense), Generics (I love generics) and LINQ.

So what is LINQ and why is it so important to add to your arsenal of .NET skills?

LINQ is so many things

At its core LINQ is exactly what its acronym suggests: Language Integrated Query. But what does this actually mean? Is it just some marketing hype designed to confuse the masses and look good on your resume. Probably. But the value of expressing a query concisely in the language of your choice becomes more apparent with each LINQ query you write. (Yes, I know LINQ Query would stand for Language Integrated Query query. Just go with it, it reads better.)

Importantly a LINQ query separates defining what you are looking for from how to find it. This means that a LINQ query could potentially be executed across multiple CPU cores and in the case of LINQ to SQL can be turned into an efficient SQL query so the hard work can be done by your database server.

But when are you going to actually use LINQ? Chances are good that you already have some code that could benefit from a bit of LINQ.

Take this example:

var itemsUnderOneDollar = new List<Item>();
foreach (var item in items)
{
   if (item.Price < 1)
   {
      itemsUnderOneDollar.Add(item);
   }
}

In this case we want to find all items that are under one dollar. The same in LINQ would be:

var itemsUnderOneDollar = (from item in items
                           where item.Price < 1
                           select item).ToList();

We can ignore the variable declaration for now (and the call to ToList()) so let’s break it down to just the core LINQ query.

from item in items
where item.Price < 1
select item

The LINQ query describes exactly what you want and nothing more. When we used the foreach construct we were resigned to the fact that we had to look at each and every item. We are also doing all this in a single thread. In fact, we spend more time describing how we want to find the items than saying what it is that we want. By describing what we want using LINQ we don’t bother with the implementation details resulting in cleaner code and improved flexibility for how the query should be implemented. In the case of a database query, the ideal implementation would be to generate a SQL query, execute the SQL query against the database and return the results. LINQ to SQL does just that with essentially the same code (I’ll be discussing LINQ to SQL in depth in another post).

From Where Select vs. Select From Where

If you are already familiar with SQL you may be a little confused by the syntax of a LINQ query. Indeed this is a major stumbling block most people encounter when they start to use LINQ. In SQL we have the ‘select’ statement upfront but in LINQ we save it for the end. Why? The primary reason for this choice was to enable great IntelliSense support in Visual Studio.

I’d like to argue that the syntax in LINQ actually makes more sense. Rather than starting with what we want at the end we start with the subject of our query. The reason this seems so foreign is that we are so used to it because of SQL. When you write code you typically say where you want to look before you say what you want to do when you’ve found it. In natural language it is like saying “From the store find a computer with 2GiB RAM and get me the price of the computer”. In SQL speak that would be “Get the price of the computer in the store where that computer has 2GiB RAM”. You tell me which form you’d be more likely to use.

The magic of type inference

Another convenient way to remember that from comes first is to think of the old foreach implementation. You’ll find that they have a lot in common. The biggest difference is that in the foreach loop we have to explicitly specify a type. In the example above I’ve used var to let the compiler infer the type. In the LINQ query the type is inferred automatically unless you specify it explicitly.

Type inference is used throughout most LINQ usage to simplify code and to improve maintainability. Queries return an object that implements the IEnumerable<T> interface. More advanced queries can return objects that implement a more complex interface (which is also an IEnumerable<T>). By using var to let the compiler infer the type of object returned by the query it saves the programmer from having to explicitly work out what type of object is returned. The full significance of this will become apparent in future posts.

How to get started

The best way to get started working with LINQ is to read up about it on MSDN. Then download the great tool LINQPad. LINQPad has some great sample LINQ queries and lets you play with LINQ outside of Visual Studio. It’s great for writing short snippets of code and is an ideal sandbox to try out bits of code. LINQPad is free, but Auto Completion is a paid feature (but well worth it). It also lets you run LINQ to SQL queries on a SQL database (and now SQL Compact Edition).

Once you have started familiarising yourself with LINQ you should start using it in your projects. There are two key requirements to using LINQ:

  1. Your project must target version 3.5 of the .NET Framework.
  2. You must include using System.Linq; to reference the LINQ namespace in all code files where you want to use LINQ.

If you don’t have Visual Studio 2008 you can download one of the free express editions from http://www.microsoft.com/express/. Once installed you might also want to turn on line numbers in Visual C# Express.

More to come

There’s plenty of stuff to talk about with LINQ. In my next few posts I’ll cover Extensions methods, Lambda expressions, LINQ over objects and much more.

Telstra’s Complaint Process (Part 1)

February 6th, 2009

Recently I’ve had some issues with my Bigpond Cable internet connection. Even more annoyingly I ran into far too many roadblocks while getting it fixed. These roadblocks culminated in a long letter to Telstra listing my grievances.

(Please redirect this complaint to the necessary area)

Hi,

I recently experienced issues with my Bigpond Cable internet connection. The problem was an issue on the street. Despite spending over an hour on the phone trying to explain the situation the service representative who eventually came out was not made aware of any of the information that I had provided your operators despite assurances that they would. Fortunately the service representative that came out was competent enough to investigate and resolve the issue (which was a connector on the street).

So my first complaint is that the information that I provided was not given to the service representative despite assurances otherwise. Either your process is flawed, I was lied to our your operators were incompetent. (Or all of the above).

My second complaint is that it took nearly a week to send a service representative to my home. That length of time is ridiculous and I hope that you work to reduce that time, especially for long-standing customers on the highest level plans who are already paying a premium for your alleged service.

As the outage was going to be so long I requested temporary access to a complementary (free) dial up account. After being shunted between multiple operators I was finally put through to someone who claimed they could help me. Yet in order to set up this free account I was asked what I believe to be very probing questions to check my credit rating (e.g. marital status). Why is this information relevant for a free account? Furthermore, why is this information relevant when I already have an account that I am paying $139.95 a month for? Perhaps your levels of bureaucracy and rolls of red tape complicate this, but to require this information for a free account is absurd. That is my third complaint.

This morning I received notification that my online bill was now ready. When I checked it I was not surprised to see another example of the incompetence of your staff. I was charged for the dial-up account during the period. That is my fourth complaint.

So it seemed necessary to once again inflict the pain of your call centres on my ears. I called the number (13 22 00) as listed on your bill and was answered by a wonderful piece of voice recognition technology. It asked me what I was calling about, I responded “Wrong charge” and it offered me great rates on STD calls. When it asked again it was fortunately able to recognise that it was a billing enquiry. Unfortunately the machine then insisted on getting a phone number, a piece of information that is not relevant to my bigpond bill at all. When I answered “No number” the machine showed its dry sense of humour it listed a series of seemingly random digits and asked if it was correct. Finally I think the machine gave up and I was transferred to what I can only assume to be a living breathing person. Unfortunately the machine was nicer and had more personality. When I explained the situation (the extra charge if you’ve forgotten) she informed me that I had called the wrong number and she gave me a new number to call. This is where my fifth complaint comes in. I asked why I had to call another number as I had called the number printed on the bill. I was beginning to feel like I was still talking to a machine when the operator responded again to call the other number. She had not answered my question at all. I asked to speak to a supervisor and she told me to call the other phone number. It seemed as if your operator was taking cues from the voice recognition system and was stuck in an infinite loop. I asked why she couldn’t put me through and yet again I was told to call the number. When I ask a question I expect it to be answered (and furthermore I expect it to be answered truthfully) even if the answer is “I don’t know”. I hope that honesty isn’t too much to expect from Telstra.

So I finally gave up and called the other number and spoke to a lovely operator who I hope (but I’m not yet convinced) removed the charge from my bill. Finally someone who seemed competent and was friendly. As she had sorted out my billing issue so well I thought I would ask her about another issue that concerned me about my bill, the credit card surcharge (my sixth complaint).

I accept that there is a credit card surcharge for the bill (despite the fact that I’m already paying a premium), but what baffles me most about this charge is that it is charged on the next bill. This leads to an exciting bit of maths known as compound interest (it may be known inside Telstra as squeezing the customer). I’ll use an example to explain what I’m saying.

Let’s say the total of the bill is $100. I pay that by credit card. My next bill includes the credit card surcharge of $0.69 (0.69%), so comes to a total of $100.69. I then pay that by credit card. My next bill has a surcharge of $0.694761 (I’ve listed all the decimal places to emphasise my point). Over time the absolute value of the credit card surcharge gets larger and larger. Although this is a very small amount I believe this to be crooked behaviour and if there isn’t already a law against it there should be. This is a practice I would like to see Telstra stop. If you must charge a credit card surcharge it should be charged at the time of the transaction and be inclusive (and therefore not compound on to the next bill).

Let’s get back to that dial-up account again for my seventh complaint. Naturally I needed to get the access number for the connection (I’d already been given the user name and password). I also wanted to see if there was any additional settings that I might need to configure. Speaking to your technical support person it was a real challenge to just get the access number, but rather I was quizzed on the type of modem, told to click particular buttons, etc. I wasn’t asking how to set up a dial-up account, but rather the information to connect to one. Eventually I was able to convince the operator to go off script and give me just the raw details, but I shouldn’t have to fight to get that. I understand that some people need this extra help, but others don’t and you can make better use of your time (and mine) if your operators take their cues more from the customer than their script.

Finally I’d like to talk about the quality of your dial-up service. Actually I don’t think I can use the word ‘quality’ in that sentence. Your dial-up service and ‘quality’ are miles apart. In case you hadn’t picked up yet, this is the subject of my eighth complaint. It was slow, terribly slow, slower than dial-up should be. Slower than my old 28.8 connection. I was lucky if I could sustain 1KB/s. It was unreliable, packets were dropped constantly and many pages refused to load (including your bigpond homepage which is so bloated it’s painful to use on broadband). How anyone can justify paying for dial-up is beyond me. I hope for the sake of the sanity of your remaining dial-up customers that this is not the norm.

Thank you for getting to the end of this complaint (hopefully you haven’t just skipped to the end) and hopefully you will be able to take some lessons from this to improve your service in the future. I would expect from you a response to each of the eight complaints I have listed above and what you plan to do to improve the service so I do not continue to experience this level of frustration. I don’t believe this is too much too expect from a large organisation such as Telstra and taking this step would go a long way to demonstrating a commitment to customer service and restoring what little faith I have left in your company. I would also expect whatever response you provide to be honest and free of marketing hyperbole.

Thank you for your time,

Rhys Parry

Due to the wonderful boilerplate disclaimer on the bottom of their response I won’t include the actual response, but rather paraphrase the key points.

  1. There was a boilerplate one sentence apology.
  2. They suggested I call their customer service hotline. Ironically this is the number I put in my original email and had issues with the service.
  3. They also gave me a separate phone number to call for my Bigpond specific issues

Most interestingly they seem to have completely missed the first line of the letter which asked for it to be forwarded to the necessary area.

I am currently writing a reply which I shall post soon.