Distance Debugging Logo

Often in the course of a longer bug investigation, you will discover one or more errors or problems that appear to be unrelated to the original bug. You may be tempted to make a note of these problems and get back to the problem you were working on. I would instead advise that you fix what you find. Here is why:

  • Unless you are absolutely certain you know what the problem, you can't know that the problem you found has no bearing on your primary issue. It might be contributing to the problem, hiding the problem, creating a first-order problem for which your problem is a second order, etc. I am shocked at how often fixing what appears to be an innocuous or seemingly totally unrelated bug will reveal critical information about or even fix the bug I started with.
  • Sometimes the process of fixing the found bug will refresh your memory about some other section of code or otherwise give you a mental break from the primary bug, and that can often trigger new approaches, new ideas, or make you look at something you hadn't previously considered looking at in the original investigation.
  • You've got the bug state "swapped in" at the time you find the bug.  In other words, the information about what is wrong, how you found it, how you demonstrated the problem, etc is all in your working memory.  Even if you do a good job of recording all the details, chances are that you will need to spend some time swapping this information back in and walking through the code at the point you come back to it, so it's inefficient to come back later.
  • As long as you have another known bug that turned up in the course of another investigation you can never rule it out as a possible factor.  This might not seem like a big deal, but speaking from experience, nothing is more annoying than exhausting a series of fixes that have no effect on a bug, and having this gnawing feeling in the back of your mind that the secondary bug you found is really to blame, for reasons not currently clear.  Leaving this "trapdoor" in your reasoning  unnecessarily complicates your investigation.

Fixing what you find will lead to better code overall, and I guarantee that you will save time and energy in the long run.

A recent post on Slashdot discussed Microsoft's response to the results of a group who discovered several what they call denial of service exploits (since that term is somewhat loaded, I am reserving judgment) in Word 2007 using a fuzzer. Microsoft responded that crashing on these ill-formed unparsable inputs was in fact the correct behavior, and so it constituted a feature and not a bug. The follow-up debate on Slashdot was, as usual, enlightening and frustrating all at once. There was the usual Microsoft bashing, a lot of discussion about bugs v. features, and quite a bit of argument about what constitutes good programming and what standard of perfection a program should aspire to.

The core of the debate is a big question: when is crashing the right thing to do? On the one hand, you have many people arguing that crashing is never the correct behavior since a crash implies that the program attempted to do something bad or illegal. On the other hand, a well-respected book like "The Pragmatic Programmer"contains an extended discussion of why simply crashing ("Dead Programs Tell No Lies") instead of allowing second-order data corruption or other damage to occur is a Good Thing. Ultimately, the answer is probably somewhere in between.

There are three real issues that need to be addressed when something goes wrong: notification, consistency, longevity. Notification to users or developers that something went wrong should be orthogonal to the question of crashing or not crash. Certainly crashing tells people that something went wrong, but it's not a very useful message, especially if there is no good reason to crash. On the other hand, I've covered the topic of silent error-handling ad nauseum in this space, so my viewpoint should be clear, but as long as your strategy of notification is not dependent on crashing, you should be fine.

Consistency is trickier. In a perfect world, every system you build would be able to meet the Consistency requirement of an ACID-type transactional system. In other words, you could at any point choose to begin tracking your system state, and if something went wrong, perfectly undo the sequence of operations that it took to get there. If you could make that guarantee, you could eliminate the concerns of the Pragmatic Programmer since you could say, "It doesn't matter if I let things continue after an error, because I know I've rolled back to the state before I started to do the thing that failed". This is unrealistic for several reasons. First, many operations have no good notion of undo. Second, you would likely have to graft a very sophisticated set of nested transactions onto your system, imposing a massive overhead in performance, and possibly code complexity. Third, fail or don't fail is not really a valid outcome for most systems. For instance, you couldn't build a browser where the system either successfully rendered the entire page, or refused to render anything. Graceful degradation in the face of invalid inputs is also desirable, even if it opens up the possibility of leaving things in an inconsistent state.

Finally, there is the issue of longevity. Crashing is simply not an option for some systems. I have built servers for which "it got an error and exited" is not a valid outcome, and in general, no one wants their applications crashing all the time, even for semi-valid reasons of prevention of data corruption.

So are there any good rules for when to crash? Crashing seems like the right option under the following three conditions:

  1. The problem is fatal - Set the bar high. Crashing should equal program panic, not program annoyed or program concerned.
  2. A human must take some action to correct the problem - It can be hard to know when this is necessarily true, but if the condition encountered does not appear to be programmatically addressable, and it is fatal, crashing makes sense. For instance, many systems require configuration files to exist, be well-formatted, and exist in certain places on disk. If they do not exist, it's often better to crash than to try to come up with sensible defaults, which often just serves to confuse the user who can't understand why their configuration is being "ignored" when they put it in the wrong place.
  3. Rollback is impossible or meaningless - If the application's best effort to contain the damage has failed, or the extent of the problem is totally unknown, or rolling back would mean rolling back to essentially nothing, and it meets the above conditions, it's time to crash.

So does the Word 2007 crash meet these criteria?

  1. Fatal? Sort of. The malformed doc means that Word's only raison d'etre, to load a document for display and editing is impossible.
  2. Human-addressable? Yes, this one it clearly meets. Word can't possible know what the "intent" of the document was in order to repair it programmatically.
  3. Rollback Failure? No, in this case there is a clear rollback position which is to restore the application to the state it was in before the load was attempted. Taking the whole application down in this situation is overkill, to put it mildly.

So in my opinion, the Word 2007 crashes fail to meet the necessary standard. Ultimately, the problem with crashing has to do with intent. It seems fairly clear that the Microsoft rep is claiming that the behavior was something they put in intentionally, when in fact it was very likely totally unexpected. If you are going to adopt a crash-sometimes-ok policy, you need to make sure you only crash under the right conditions and not because you simply failed to build a robust system.

How is task management like gambling? There is a direct connection in selecting a set of tasks to fill a timebox. You are essentially "betting" a certain number of work hours, and therefore dollars, will be enough to complete a particular task. Let's leave aside for the moment the question of whether that task will result in something of value and assume that every completed task produces an amount of value that is linear with the cost of execution. That way we can say that 8 hours of effort that results in a completed task is worth 8 units of value.

The problem is, we don't know a priori how many hours will be necessary to complete a task, which is where the risk comes in. In general, we hope that increasing the number of hours spent on a task increases the likelihood of completion, although that isn't guaranteed. Standard project estimation techniques rely on hi-low ranges such as "Task 8 will take from 2-8 hours to complete". What we might really be saying is, "There is a 10% chance that this will only take me 2 hours, and a 90% chance it will take me less than 8 hours". The percentages change from task to task, and it could be %90 less than 2 hours and 95% less than 8 hours for another task, which is why the hi-low estimate leaves out a lot of information.

I like to turn this around and state the range as: "If you give me 2 hours, there is a 10% chance that I will complete the task, and if you give me 8 hours, there is a 90% chance I will complete it". That's nice from a management standpoint, because the number of hours you have are generally fixed, so you can look across your tasks and decide how high you want to raise the percentage for any given task. The output of a timebox then is a set of completion probabilities. Ideally, you group things by low, medium, and high probability of success in order to get a sense of what is likely to be the state of your system at the end of the timebox.

What about when you don't have any idea how many hours are needed for a particular probability of success? That's where the metatasks that involve improving your task estimates come in. Whether or not you think of these estimation tasks as metatasks, most projects do quite a bit of them. Processes such as CMM are oriented around helping you provide the best estimates possible. But how much are better estimates worth?

Let's say you have a task that you think will take between 8 and 24 hours to perform. How much it is worth to you to know that it will actually take between 6 and 15 hours (reducing the max-min ratio of 3 to 1.5)? It depends on the cost of a bad estimate. The costs of estimating too low include deadline slippage, loss of trust from stakeholders, and overwork of employees trying to use heroics to meet impossible estimates, to name a few. Estimating too high has other costs, the main one being that tasks tend to fill the space allocated to them, so high estimates lead to developers spending unnecessary time on things. Even if this effect is tempered, there are other problems such as having to constantly rebalance to add more tasks to a timebox, and just simply looking less efficient than other comparable groups.

Your decision about whether or not to improve your estimate depends on how expensive you believe over and under-estimation to be.  In my experience, underestimation is significantly more costly than overestimation, so I like to improve my estimates when they look suspiciously low, since that tends to result in the most benefit.  Overall, I don't like to spend more than about a quarter of the low-estimate time on better estimation since you are usually better off spending that time on doing the work.

Integrating these estimate-improvement tasks into your timebox along other metatasks, plus the addition of success probabilities to every element means that you can conceive of your outcome as what we will likely have done, and what we will likely know.  Making this switch can often help communicate to stakeholders better about what you are accomplishing, where you see the risks, and where you are taking your gambles.

When you think of generating a list of tasks for a particular timebox, you generally think of things that are construction-oriented. For instance:

  • Implement Feature X
  • Fix Bug Y
  • Refactor API section Z

which all involve actually constructing or changing a piece of code. However, there are a bunch of other tasks that are process-oriented, which are usually verbally stated in the minutes of meetings or implicitly executed by the management team, but which show up on no formal task list.

For some reason, software managers (and maybe managers in general?) have an aversion to comingling development and process tasks into a single planning system. While some management tasks don't make sense in this way, like ongoing activities or recurring reporting, I've noticed that by keeping everything in one place and by using some simple metatask designations (tasks that involve working with other tasks instead of with code), the schedule becomes much more transparent and manageable.

Here are a few metatask designations that I commonly use:

  • Task Scope Analysis - This is the general heading for tasks whose outcome is simply more knowledge about the scope of another tasks. There are a few subtypes of this:
    • Increase Estimate Quality - The most generic form of analysis attempts to take a task that has a wildly varying or low-confidence estimate and either narrow the estimate range, or increase your confidence level.
    • Cap Scope - This is a very specific kind of tasks that involves taking a broadly-defined task (such as a typical Chop) and breaking out a more limited set of well-defined, and time-capped (i.e. this must take no more than 2 hours) tasks. This metatask is useful when you have a strongly fixed amount of time, but nebulous tasks that need to be tightly managed.
    • Go/No Go - Many development tasks are not required for the success of a project, and as any software manager will tell you, you spend much of your time figuring out what you don't need to do. This metatask makes the work that goes into those decisions explicit.
  • Triage - This idea should be well-known to most managers, but it's rarely explicitly stated. In short, going through some set of tasks and organizing them by priority.
  • Schedule - We generate schedules all the time, but we rarely put "generate the next schedule" as an item on our current schedule. It is implied that by the time one timebox finishes, the next one will be ready to go.
  • Balance/Rebalance - During a timebox, do two thingsL 1) look at each developer's task list and determine if they have too much, not enough, etc. and redistribute tasks as necessary 2) if you are overscheduled, knock some tasks out of the timebox, and if you are underscheduled, bring in some tasks from the on-deck circle.
  • Purge - I don't know if I've ever seen this as an explicit tasks, but the idea is to go over your task list and just get rid of tasks. This can be for many reasons: it was a dupe of something that's already done, the task is OBE but was never discarded, or the task is so poorly described or understood that it will never get scheduled in its current form.

Using metatasks has many benefits. I've already mentioned the transparency aspect. To me, the biggest benefit is the transformation of a timebox outcome from simply "we added these features, and fixed these bugs" to "we added these features, and fixed these bugs, and acquired this knowledge". In many cases, gaining the knowledge of the scope of a task is as valuable as the task itself. Without a metatask, we are forced to schedule the task directly, and treat the analysis of its scope as part of the task.  Another benefit is the ability to schedule when information will become available.  A great example is the Go/No-Go metatask.  In timebox N, you schedule a series of Go/No-Go metatasks on a handful of tasks that you might schedule in timebox N + 1.  This guarantees that by the time you needed to add the task or tasks to the schedule, the information about which one or ones you plan to do is already available to you.

With or without metatasks, the problem of how much time is spent analyzing a tasks or set of tasks, versus doing them is a constant struggle, and is the topic of the next post.

Next: Task Management III: Task Management as Gambling

Well, I've made it to 100 in only 6 months. I've installed a new plugin called Bad Behavior, that is supposed to block SPAM bot access attempts, which I guess appear as distinctly different accesses from regular folks. If you get blocked, I apologize, but I've been forced to look through 100+ comment SPAM messages a day in Akismet and I'm more afraid that some legitimate content will get flushed accidentally at this point, so I've stepped it up a notch. Time will tell if this solution meets its promises.

I've been thinking a lot about software project management lately, specifically all the pieces that they seem to leave unspecified. These next few posts attempt to fill in the gaps with some of my experiences and thoughts.

One discussion that is conspicously absent from most, if not all, software project management methodologies is the question of task scoping. The two agile methodologies with which I'm most familiar, XP and Scrum, both have notions of organizing tasks into timeboxes based on priority and estimated time (i.e. do the most important things in the current timebox, and select a set of tasks that will fit into that box). When I've tried to apply these ideas in practice, I'm always left with the same question. How do I handle tasks with a totally unknown or wildly varying estimate? Most tasks can be scoped as some value * or / by 2, but some tasks have a range of 10 or even 100 to 1 from the high estimate to the low estimate. I've seen too many projects where the desire to get something done and fit it into a timebox causes people to lowball estimates or put an arbitrary cap on the high estimate in order to make it work, thereby defeating the point of the timebox.

I've developed a simple system that uses basic task classifications and metatasks (more on this tomorrow) to handle this issue without sacrificing good estimation or timeboxing. To begin with, every task is given a designation: Chop, Craft, or File. Image you are making a piece of sculpture from a block of stone. When you begin, you must "chop" large sections of the block away in order to approximate the shape of the final figure. Once that is done, you execute the more artistic and careful "crafting" of the actual figure, including the shapes of the arms and legs, the torso, and face. Once a particular portion of the figure has been crafted, it must be fine-tuned through controlled, meticulous "filing" such as creating fingernails, adding detail to the hair, and so on.

These steps can be translated directly into software tasks:

  • Chop tasks, which tend to cluster towards the beginning of a project, are the more open-ended infrastructure design and construction tasks that provide the foundation for the main system. They are the tasks that most methodologies seem to pretend don't exist, but which lead to the most headaches for projects because they are so variable in scope.
  • Craft tasks are the most fun, in general, because they tend to be clearly scoped and deliver clear functionality. Craft tasks generally produce the most code per unit time because developers can crank out features, building on top of what was "chopped" out earlier.
  • File tasks are the least fun, in general, because they are all the little annoying and often tedious things that separate a quick-and-dirty prototype that has been crafted but never filed, from a real usable system. These are tasks like "Fix the logic that disables the buttons at the correct time" or "Make sure every database connection error is properly reported". They can be especially unpopular because the result is often mostly invisible or rarely encountered, leaving developers little to show for their time other than a more robust or usable system. File tasks also consume a massive amount of time on any project. The 80/20 rule (or whatever split you may use as a rule of thumb) results from the fact that Craft tasks churn out so much code in so little time that they give you a false sense of the remaining work, which are the time-sink Filing tasks.

There is a second problem though, which is that you often don't know a priori what kind of task you actually have. Sometimes a Craft will look like a Chop, or a File like a Craft, or vice-versa. That's where the introduction of tasks to help you understand and organize your tasks comes into play.

Tomorrow: Metatasks

I was trying to help someone copy settings (email, web bookmarks, etc) from one Windows machine to another and they asked "Why is it so complicated to copy my settings?" The true answer, at least in my mind, is some combination of: each program handles its settings information differently, Windows doesn't provide a standard mechanism for transferring settings, and Windows tends to store pieces of a program in a zillion undocumented places making it difficult to do a post hoc gathering of the data. Each of these pieces would require some back explanation ("applications need a place to store your preferences so that it can remember them between usages"), and so there is a natural tendency to summarize and compress.

This compressed answer, something like "The information is stored all over the place", is basically correct, but it isn't the whole story. It sufficient in this case, but often a choice has to be made between giving a long, complicated answer and giving a terse but somewhat incorrect answer. I call this line beneath which any compression would result in incorrect information the "Irreducible Correctness Boundary". The problem is, the boundary is not fixed, but it varies depending on the sophistication and skill of the recipient.

I was reading "Fortune's Formula" recently and he posed the example of two different people asking if the world was round. The first was someone from the middle ages where the general conception was that the world was flat (I believe the theory of widespread belief in the earth's flatness has come into dispute recently, but bear with me), versus someone from modern times studying the shape of the earth. To the first person, an answer of 'yes' is a signficant amount of information and additional content regarding the non-perfectly-spherical nature of the earth is more harmful than helpful. The modern person, who knows that the earth is at least roughly round, might benefit from additional information that the earth is not really spherical, but an unqualified 'no' is a much less correct answer than an unqualified 'yes'. The real problem is that the irreducible correctness boundary is much higher.

Substitute a non-technical and a somewhat-technical questioner for the two people in the previous example, and you have a situation that technical people are encountering all the time. If you guess too high on someone's level of technical sophistication, you will swamp them with unnecessary or even misleading details. On the other hand, if you guess too low, you can quickly drop below the correctness threshold by omitting important details. Estimating a particular person's level of technical sophistication can take time, but it's critically important to know when passing data back and forth, especially when using someone as a trusted debugging contact.

As those of you who read my earlier posts about Linux and T-Mobile Dash may remember, I had a couple of lingering issues (you can read part I and part II). Well, with a little help from the internet community, my two biggest issues are now solved, and I believe that there is little or nothing that can be done on Windows that can't be done on the Linux platform.

  1. PIM Synchronization - After correctly patching my synce installation, I was able to sync contacts, calendar, tasks, and they've just added file sync support so that you can synchronize your smartphone file system with a directory on disk.  PIM stuff is under heavy development with new features and bug fixes all the time.  If you are interested in testing the bleeding-edge code, subscribe to the SynCE Windows Mobile 2005 mailing list for more info.
  2. After a kind WINE developer read my earlier post about problems installing ActiveSync, he took it upon himself to fix the issues and now ActiveSync installs correctly (see his posts on my earlier T-Mobile dash posts for more info).  I then was able to use WINE to run one of the standalone program installers (gnuboy, an opensource game boy emulator), which was "fooled" into thinking that ActiveSync would install it on the next sync.  Instead, I grabbed the .cab file that it unpacked into the ActiveSync directory, and was able to install that directly, so now there shouldn't be any problem installing applications either.

    After discussing this with the Windows Mobile 2005 SynCE list, there is some talk of other ways to accomplish this, such as determining how an application tries to check for ActiveSync, and tricking it so that you don't have to install ActiveSync in the first place, or writing/improving a tool to scan through these special installers looking for the .cab header, so this won't be last word on this issue for sure.

Hope this helps those of you concerned about Dash support on Linux.  After another month or so of use, I am extremely pleased with the whole setup.

The site has begun to approximate the layout I had in my head, with the content boxes encircling the center content.  If you have a smaller screen, you may have to scroll the outer window to see the whole page, but I'm hoping for the majority of visitors, the content will appear in its entirety, and you should only need to scroll the interior box to read the posts.  I may shrink the scroll box a bit so that it will fit even on a small screen (like my 12" laptop screen).

I am still somewhat baffled by the notion that DIVs and SPANs are somehow supposed to replace tables, since for the life of me, I cannot get any combination of them to layout in the simplest possible side-by-side configuration.  For instance, the group of three boxes beneath the post scroller was done with a table.  Why?  Well, you can't just use DIVs, because they break afterwards since they are block element so they stack vertically.  You can't just use SPANs, which don't break, because at least as far as I can tell, because I am using DIVs inside to represent the title and content of each box, the SPANs non-breaking aspect is ignored.  I'd switch the titles to be SPANs instead of DIVs, but SPANs don't accept a width or height attribute so all my title boxes would be different sizes which is visually unappealing (at least ot me).  I'd use DIV with style: inline, except that has the same problem.  I'd float them all to the left to get them to stack up, except then I'd need to put in some kind of placeholder DIV to bump the footer down, and the float property seems to be the thing I understand least because I guess it pulls those elements out of the static layout and adds them to their own layout so I'm even worse off when things start to go wrong.

But hey, I create a table with three cells, set the margin information, stick my two DIVs in each cell and voila, a nice, simple side-by-side layout that is easy to understand and debug.  If some HTML guru out there who really understands this stuff can explain to me how to get the effect I'm looking for here, I'd love to have a better understanding. Hours of web research have turned up very little that would work for me here.

I had a Citibank credit card. Technically, I still have it but I will be closing the account shortly. My recent experience has been a not-so-rare glimpse at how strange the business practices of a mega-corporation can be sometimes. This is not a story about fraudulent charges or bad customer service, just a sequence of events that made me doubt their ability to competently run their own business.

1) The story starts a few months ago when we received an ominous letter telling us that our card had been compromised and that we needed to contact their customer service people immediately. With us both in full possession of the cards, our overall rare use of the card (it's mostly backup for the rare occasions when our primary card won't scan or decides to go on fraud-alert freak out, or for when I'm traveling to help keep track of expenses), and my fairly elaborate attempts to keep our personal information protected (crosscut shredder, receive electronic billing statements only, etc) I wondered how that was possible and wondered if it was some kind of Citibank ad campaign.

It turns out that they were the ones who were compromised; Citibank had lost our information, and then sent us a letter making it look like they had ingeniously detected some fraud and proactively addressed it. They insisted on sending us a new card and an affidavit in case we needed to report fraudulent activity. They were not amused by my wife's sardonic questioning regarding their ability to protect this new account number any better than the previous one.

2) When I received the new card,we had to call to activate it as is customary. Normally, you dial a number and get an automated message asking you to enter the number and then it is activated almost immediately. We recently received a new card for our primary credit card account because it had expired and this is exactly what was necessary to activate the card. Citibank on the other hand has "pioneered", at least I experienced it with anyone else, the use of a live person to activate your card when you call. This provides them with the opportunity to cram some sort of up-sell gimmick down your throat when all you were expecting was an automated message.

This time around it was the new "credit protection" plan that everyone wants to sell you, where for a fee based on your balance, you can skip a payment (or 2 or 3?) if you lose your job or you have a serious medical incident or one of the other horrible qualifying conditions. I tried to explain to him that regardless of the fact that I don't take part in fear-based nonsense, if I instead took the money I would have been paying for the credit protection and stuck it in a savings account, I would easily be able to cover a minimum payment for a few months. Insurance only makes sense if the gap between the cost to insure and the cost to replace/repair is high (home insurance), or qualifying conditions are likely (car insurance). In this case, it wasn't even close. I wasted 10 minutes of my life trying to get off the phone without being rude, and then finally resorted to being rude since I had only called to activate my f&@$ing card.

3) After activating the new card I went to log on to the card site to check that everything was in order. Of course, I was naive to assume that because this card was the logical successor to the previous one, that card would show up. Instead, it showed our old account as closed and nothing else. I was forced to create a whole new username and account for this new card. That was annoying, but not terrible. It will be turn out to be important later on though.

4) This is more of an annoyance than a real part of the story: Citibank revamped their credit card website, and now whenever you go there you get a pop-over add thing that blocks out your access to their actual website until you click a few things. It's like they created a DIV that they position over the site, but instead of just making it the size of the ads, it blocks out the entire site with white. And this is just to show me ads for a product I already own. Keep in mind that I had to click through that thing a zillion times during the course of the ordeal I am about to describe.

5) A few weeks ago, I made a payment on the web site scheduled to take effect a few days later. A day after the day it was to be debited, I got an email from Citibank thanking me for my payment. I went and looked at our bank account and there was no charge. That seemed odd and I chalked it up to a timing glitch with online processing. When I checked the next day, still no charge. Then it hit me, I had another bank account that I had been using previously but which now had like $100 in it but I was too lazy busy to close. Going to look at that other account, I noticed that sure enough, a charge had been attempted and of course rejected for insufficient funds.

I was really mad for 2 reasons: first, I had no idea that bank account was even on that new card's account. It had been on my old Citibank card, and without telling me, Citibank had brought over that old information to the new account I opened. So it never occurred to me to double check that I had the right bank account selected because I didn't even realize that there was more than one. Why they could link my bank account after the fact, but forced me to create a whole new card account is beyond me. Second, why can't they ask if you have the funds available without actually debiting it? It seems silly for me to pay a bounced check fee when there is no person receiving the check, and no goods changing hands, etc. Why should electronic funds work exactly the same as physical funds? Especially with a huge conglomerate like Citibank would could save themselves and their customers huge hassles with a better system.

6) Now it got fun. My credit card statement showed that it had been paid, despite the fact that there was evidence that it had been rejected by my bank, so I couldn't just make another payment and be done with it because you can't pay more than your remaining balance. I called customer service who explained that they must not have been notified yet (which seems unlikely since it showed up in my online banking statement) but that the problem was that Citibank might keep trying to debit the amount depending on "what response they received back from my bank". So I was supposed to call my bank and find out how they would have marked the rejection, either as insufficient funds, or just denied. If it was insufficient funds, they might just keep retrying, with me incurring a new charge every time! She insisted that this was all done electronically and her hands were tied.

I called my bank and actually got someone who understood what I needed to know. He advised me that given that it wasn't an active bank account and had only a small amount of money, I should just close it and make it impossible for them to retry and incur additional charges, which I did (thanks guy at bank, you are the only person in this entire thing that had any interest in helping me!)

7) Fast-forward to today. Ten days after I originally got the bounced check notification, Citibank finally notified me about it. The best part is that their email says, in a nutshell "since your bank account was closed, we have disabled your on-line payment functionality until you call customer service". Remember that I have a perfectly valid bank account that I have used several times before to pay this card, and it was only because they decided to bring over an older bank account unexpectedly that this happened, and that I had already spoken to their customer service to explain the problem and was given essentially no alternatives. So now I can't pay the bill on line any more and have to call them. I guess I would have needed to call to close the account anyway.

The thing that bothers me most is that Citibank will feign interest in my leaving them when I call to close it, but they ultimately don't care about people like me except in some sort of aggregate statistical way. I feel at every turn their message is "we don't really care about making you angry or costing you time and money if it means we might make a few extra bucks because of sheer volume", which I'm going to call the "spam theory of business". After a decade of card membership with an ever increasing level of disinterest on their part, it's time for me to stop rewarding them.

Syndicate content