Archive for November, 2007

Bug Reporting for Users

A constant source of frustration at my workplace revolves around bug reporting.  In general, this is a touchy situation anyway.  Developers are proud of their work and really don't like being put down.  Users have problems that they want fixed, but don't have any way to do it themselves.  Furthermore, many end users don't have the capability to express their issues in an effective manner.  Here's a few tips for users that hopefully can make things easier.

  1. Stop - If you encounter an error, stop.  Not forever, but it's important to pause for a brief moment in time so that you can do the things below.  Blind button pressing and clicking doesn't do a damn thing but make more crap go wrong.  It's important to take notice of the first thing that happens, not the very last thing after you got tired of wildly clicking on the screen.  Once you find a problem, all bets are off.  Chances are more stuff won't work after you've encountered an issue
  2. Calm Down - Freaking out doesn't improve the situation at all.  Panic clouds the mind and spreads like wildfire.  Don't get upset and definitely don't yell.  If you cause a stink and throw a tantrum you'll make me hold a grudge.  It's hard to help someone that always has a problem and it's always a priority that it be fixed 5 minutes ago.
  3. Think - The important thing for a developer in bug fixing is being able to narrow down the circumstances that produce the problem.  This requires you to trace back over your steps.  Did you do anything differently this time than you have before? If the answer is yes, then that is important to me.  If you can reproduce the problem, then that's even better.
  4. Look - Did you get an error message?  If you did, don't blindly click the Continue button and act like nothing ever happened.  Those cryptic messages are important to use.  Take a screenshot or copy and paste.  Here's where it's important for you to look back over what you did and make sure you aren't doing something stupid.  No matter how many times you click send, that email will never go to that misspelled email address, I promise.
  5. Be specific - It's important to know where you were when you encountered the error.  What program/website were you using?  What were you trying to do when you found the problem?  What action did you do immediately before stuff hit the fan?  It's also important for us to know what computer you were using and maybe even more specific things about that computer.  Hold off on the extreme specifics unless I ask for them.
    Here's a good example: This past business trip I had a problem sending email in Outlook on my laptop.
    Here's a bad example: My email doesn't work.
  6. Don't tell me how to fix the problem - 99% of the time you're wrong. 100% of the time you will annoy me. I'm not saying that you aren't smart, it's just that you don't know about the stuff that goes on behind the curtain.  I'm not trying to be a jerk here, but there are certain things that I know that you don't.  You're just guessing and in the process you're putting thoughts in my head that will lead me down the wrong path.  You do your job. I'll do my job.  We'll work through this together.
  7. Follow reporting procedure - If there is a process in place for reporting problems, then use it.  When I ask you to log a ticket it's not because I'm being an ass.  We have a ticket system to organize, prioritize, and archive our workload.  Bypassing the procedures is likely to get your issue lost in the shuffle of things.  You're also more likely to get different people working on the same thing without knowing it causing wasted man hours lost to double work.

Extension Methods meet the Database

A co-worker and I had a conversation about extension methods today. Somehow this triggered a thought that I needed to see what could be done about the database monotony. Here's the result.

/*
 * DbExtensions.cs
 * Author: Josh Bush (digitalbush.com)
 */

using System;
using System.Data;
using System.Collections.Generic;

namespace Bush.Data{
    public static class DbExtensions{        

        private delegate T DbAction(IDbCommand cmd);

        /// 
        /// Helper method that does the connection and paramter setup.
        /// 
        private static T ExecuteDbAction(IDbConnection conn, string commandText, IDbDataParameter[] parameters,DbAction dba){
            if (conn.State != ConnectionState.Open)
                conn.Open();
            using (IDbCommand cmd = conn.CreateCommand()){
                cmd.CommandText = commandText;
                cmd.Connection = conn;
                if (parameters != null){
                    foreach (IDbDataParameter p in parameters)
                        cmd.Parameters.Add(p);
                }
                return dba(cmd);
            }
        }

        /// 
        /// Extension method to execute a query and return the resulting records
        /// 
        public static IEnumerable Query(this IDbConnection conn, string commandText, params IDbDataParameter[] parameters){
            return ExecuteDbAction>(
                conn,
                commandText,
                parameters,
                queryHelper  //ugh, can't use yield inside of an anonymous method
            );
        }
        public static IEnumerable Query(this IDbConnection conn, string commandText){
            return conn.Query(commandText, null);
        }
        private static IEnumerable queryHelper(IDbCommand cmd){
            using (IDataReader r = cmd.ExecuteReader()){
                while (r.Read())
                    yield return r;
            }
        }

        /// 
        /// Extension method to execute scalar query.
        /// 
        public static object QueryValue(this IDbConnection conn, string commandText, params IDbDataParameter[] parameters){
            return ExecuteDbAction(
             conn,
             commandText,
             parameters,
             cmd=>cmd.ExecuteScalar()
         );
        }
        public static object QueryValue(this IDbConnection conn, string commandText){
            return conn.QueryValue(commandText, null);
        }

        /// 
        /// Extension method to execute a non-query.
        /// 
        public static int Execute(this IDbConnection conn, string commandText, params IDbDataParameter[] parameters){
            return ExecuteDbAction(
                conn,
                commandText,
                parameters,
                cmd => cmd.ExecuteNonQuery()
            );
        }
        public static int Execute(this IDbConnection conn, string commandText){
            return conn.Execute(commandText, null);
        }
    }
}

I haven't had a chance to really run this code through it's paces, but I did the following test. I used the MySql Connector, but the methods above should work for any database provider as long as it implements the IDbConnection interface.

using (MySqlConnection mConn = new MySqlConnection(ConnectionString)){
    foreach(IDataRecord r in mConn.Query("select post_title from posts"))
        Console.WriteLine(r["post_title"].ToString());
}

Compare that to the craptacular way of doing it without the extension methods.

using (MySqlConnection mConn = new MySqlConnection(ConnectionString)){
    mConn.Open();
    using (MySqlCommand mCmd = new MySqlCommand("select post_title from posts", mConn)){
        using (MySqlDataReader r = mCmd.ExecuteReader()){
            while (r.Read())
                Console.WriteLine(r["post_title"].ToString());
        }
    }
}

I like the improved look. I haven't had a chance to test this with transactions or much of anything else for that matter, so just consider this a proof of concept at the moment.

You can download the extension methods to see for yourself: DbExtensions.cs

« Previous Page