Gathering Running Music

I dug out my “massive” music collection, copied some good stuff over, and am currently running beaTunes – to detect BPM.  Once that’s done, I have a smart playlist for the right ranges of BPM – I’ll use that to sync to the iPhone (I have an order of magnitude more music available than space) – then run PaceDJ to pick the songs at or near the pace that I want (I usually start at 160 and speed up to 180 during a longer run).

2013-04-22 22_08_40-beaTunes 3.5.12

I’m already liking the music it has chosen so far.

First run with the co-workers tomorrow.   I am a little nervous.  They are all so athletic.

Half Marathon Running Gear

I was thinking ahead to next week, about what gear I will be running in.  It got confusing, so I made a chart.

Half Marathon Running Gear

(Click to zoom in to the larger image)

  • From Left to Right is the order in which stuff goes on.
  • In case of extremely cold weather, the stuff on the right of the purple line comes in to play

In Detail

S1 Suunto Chest Strap Captures heart rate data, sends to S2. Helps me regulate my pace and gives me blog posting material for the future.  Deploys a protective armor nanoshield in case of attack.
S2 Suunto T6d Super computer watch which receives all the data and logs it.  It will also act as defensive laser in case of alien attacks.
S3 Suunto Foot Pod Has a built in Accelerometer and FancyMoMancyMeter to tell me how fast I’m running, accurate to +/- 10% depending on calibration.   Provides extra jumpjet fuel.  Uses invisible ants to send signals up your leg and down your arm to the watch.  No joke.  ANTS protocol.
SH1 Technical Shorts. TMI: These are the kinds with the built-in sweat wicking underwear.   They have pockets that are designed to loose things when you sit in your car.   I wish that were a joke.
SH2 Technical Shirt. Probably from the Bourbon Chase or one of my other favorite race shirts.  I like the loose mesh ones better than the tight mesh ones.   Designed to make me radar invisible to the aliens.
MS9 Motorola S9 Bluetooth Headset For listening to ma’ tunes from the Pace DJ.  Also used for communication with central agency in case of emergencies.  Volume and Track buttons.  Skips in high humidity.  Can be used on single ear only.
TFMH Tin Foil Metal Hat Hopefully the aliens don’t invade on Saturday.
IFB iFitness Belt To Store the I4S and some ID/Cash.  If taken off and whirled in a circular motion, can create a kinetic shield to ward off flying kitten attacks.
G1 Powerbar Gel – Green Apple – With Caffeine 25mg baby
G2 Powerbar Gel – Vanilla – No Caffeine ‘cuz Caffeine is a Diuretic, and the aliens may mistake porta-potties as enemy tanks and destroy them first.
G3 GU Powergel – Espresso flavor Hip Hip Hipster Hooray!  (less runny than Powerbar gel)
GCS GoPro Chest Strap I didn’t want the head mount, makes me look too techy and attract attention to myself.  Seriously.    Can double as an ammo belt for paper clips.
GP GoPro Because recording video on my iPhone is not sexy enough.  Will also spot creatures using phased light invisibility techniques because of the parralax they cause in the 117-th degree FOV filter.
PDJ Pace DJ Scans existing music library for tempo – you don’t have to choose the music.   Hopefully its all good.
CB1 CamelBak Hydration Pack Only need to stop every 6 miles, and I never have to ask when the next water station is.   Can also hold keys in the zipper part.   Can be used to squirt water at the aliens, because most aliens are allergic to water, and they forget to scan before they invade.
LSTS Long Sleeve Technical Shirt For when temperatures including wind chill drop below 50F
TFFFHP Tight Form Fitting Flab Hiding Pants For when temperatures (including wind chill) drop below 40F
GL Gloves To prevent attempting to warm hands in more publicly embarrassing ways.
SJ Sport Jacket The kind with Mesh on the inside.   Combine with LSTS to get it down to 35F or so.  You generate a lot of body heat when running.
WRH Winter Running Hat Keeps them ears and the bald spot warm.  Bald spot is the center of brain activity, if it gets cold, the eyes glaze over.    Machine Washable! Didn’t know I had sweat glands there.  Oh wait, Thai Food.
IEB IPhone Ear Buds (new kind) Cuz my ears are shaped funny, very few earbuds work; when wearing the WRH, the MS9 doesn’t fit, so I have to go wired.   The square shape of the container helps differentiate them from a hockey puck.
GAUBAC Gawd-Awful-Ugly Big-Ass-Coat Purchased at goodwill, to stay warm before the race and during the first few miles; abandoned during the route; also a decoy to confuse the aliens.   Pink with leopard spots is awesome.

Yep, I’m crazy.   What the heck, I’m owning it.

In case you hadn’t figured out, italics = for fun, not real. 

The Ali Shuffle 10k 2013

About a week ago, I participated in a 10k run.. and I happened to bring a (partially charged) camera.  Turns out they’re a small organization, so after chatting to a volunteer, I took video of the race, and put this together to help promote them:  (less than 3 minutes):

[youtube=http://www.youtube.com/watch?v=UEtQqjpkf2c&w=448&h=252&hd=1]
The Race

Geeky: It was challenging to put this together – I had to renew my subscription to Adobe Premiere CS6 to get access to the Warp Stabilizer effect to calm the running parts of the video down.  (It worked very well).

Some other stuff I learned:

  • The Muhammad Ali Center is downtown.  That’s where Marcell works.
  • The Muhammad Ali Institute is at U of L, and is not the same thing.  That’s where Stacy works.
  • The Institute goes in two year cycles with its scholars:
    • Researching global problems?
    • Making a trip to study solutions abroad?
    • Bringing those solutions back home in a project?
  • This was the second year of the run.

There’s a (much) longer video with all the footage I got, unedited, unstabilized, where you can learn more from the people who talked to me: http://www.youtube.com/watch?v=qtQm5Tu7WGM

The bicycle lady.. I had dropped my GoPro, she rescued it and brought it up to me.    Thank you!

I’m thinking, this year as I do races, I’ll do little glimpses into the races .. to promote them.  Because I think they are beautiful and cool and worthy enterprises.   Especially the smaller ones.

Be good.

Boston

I don’t even know anybody who was running.  My wife does.  Yet I have been so freaked out?

I think I’m grieving the loss of innocence.  Certainly not in me.. but maybe about this sport, Running, that I’ve just started doing.   I don’t know what it is, but my heart is heavy.  And my emotions have been coming out sideways… like really weird, wavy, driving, slamming the salt shaker down harder than normal, etc.

  • Denial –  Is this really happening?  Look at every news source, scrutinize, analyze, not satisfied with the words till I saw the carnage they described.  I did find pictures of the carnage.  It became real. I needed to know how real it was. 
  • Bargaining – Is there something, anything I can do?   Can I even write about it?  If I were in Boston…
  • Anger – I usually stuff this one.   I also usually go through this last.   Hey, the salt shaker.  Its there.
  • Depression – My wife asked, Honey, why are you so down?  I’m just down. 
  • Acceptance – Not quite there yet.

I had a different blog post in mind, something about a race I ran over the weekend.  That can wait for later this week.

Geeky Side Notes:

  • #HelpBoston – thank you God for there being beauty in humanity. 
  • http://live.abc15.com/Event/Boston_Marathon_Explosion_April_15_2013 – powered by http://www.scribblelive.com/.  They became my main feed during the afternoon – well culled, well moderated, not a lot of hype, lots of facts, lots of helpful things, not a lot of speculation.  I don’t know if it was @abc15 (Arizona) that was doing the culling, or if it was some other agency (ABC overall?) but Very Good Job Done.  Thank you.
  • General thoughts of how my instinct is no longer to go to a news channel, but to find things that are more organic, more connected, more immediate – real people reporting what they are seeing, feeling, not a news organization regurgitating that which some PR person put together somehow.  
  • My coworker Dan had the idea of listening in to the EMS radio channels on the ground. 

Enough for today.  Will try to focus back to work tomorrow. 

Getting Familiar with Downtown Louisville

I’ve lived near Louisville since 2006, yet, I am somewhat lost around downtown.  I blame having a GPS – I never had to learn it.  

Well, I’ll be spending more time downtown – with http://codepalousa.com/ and the KDF Mini Marathon being on the same day, I need to get back and forth between them without a car, and stop by the Downtown YMCA for a shower.

So I started playing to learn downtown better:

image

I almost always approach from I-71, so I set a start point there, and played with “to get to XYZ place, which exit does it have me take?”   – the result is the above map (done by hand, though would be nice to have a program that did something similar.. showing the tree of routes, perhaps, into an area?).  I was surprised at how versatile the Brook street / Jefferson street (green) exit was – and how much the 9th street exit (tan?) plays into it as well.

Would anybody with more time than me be interested in writing a google app that could represent the same information as above?  

Excel C# Interop

imageI made a mistake.

I had a project in mind – to pull information from basecamp classic (todo items, with metdata embedded), massage it in Excel, and then upload it back to basecamp.   

I have always used Excel (or some equivalent spreadsheet) for project tracking – I never know which columns I need to add, when I need to highlight things in different colors, what kinds of charts I need to pull.  So the decision to bring the information down to Excel was an easy one.

That cost me 1.5 days of pain.

In retrospect, it would have been much easier to use EF Code First against .\SQLEXPRESS and use SSMS to do the bulk of my editing.   But, not having the time to re-tool, I pushed forward, and I did get it to work well enough to help me next week as I start work with that project…

Anyway, this is what I learned along the way:

Workbook.Save();
Workbook.Close(false);
Application.Quit();

The recipe for closing down the instance of Excel that had been open.   Without getting asked if I want to save.

Marshal.ReleaseComObject(sheets);

You have to do this for pretty much ever object you dink with, else the COM Excel wrapper stays open in the background. Reference: http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects). I haven’t fixed this yet.

Sheet.Cells[1,1].Value
Sheet.UsedRange.Cells[1,1].Value

Cells start at index 1, not 0.   UsedRange is faster than Cells.

var connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + fullName + ";Extended Properties=\"Excel 12.0 Xml;HDR=YES\"";

You can use ACE to query spreadsheet data, however, type-coercion is sketchy.  As in, if you have no data, that long field will come back as string.    I ended up ditching that and reading/writing my lists of stuff directly from the worksheet so I could have direct control over the types:

Worksheet sheet1 = b.Worksheets["Tasks"];
t2 = sheet1.ReadListFromSheet<MyTask>();
sheet1.UpdateListToSheet(t2, "TodoItemId");
        public static List<T> ReadListFromSheet<T>(this Worksheet sheet) where T:new()
        {
            var nameToCol = GetNameToColumnDictionary(sheet);
            var colToSetter = GetColToSetterDictionary<T>(nameToCol);

            var result = new List<T>();
            var rowCount = sheet.UsedRange.Rows.Count; 

            for (int row = 2; row<=rowCount; row++)
            {
                var item = new T();
                foreach (var e in colToSetter)
                {
                    var col = e.Key;
                    var setter = e.Value;
                    var o = sheet.Cells[row, col].Value;
                    if (o == null) continue;
                    Type source = o.GetType();
                    var target = setter.GetParameters()[0].ParameterType;
                    try
                    {
                        object o2 = null;
                        if (target == typeof(bool?))
                        {
                            o2 = Convert.ChangeType(o, typeof(bool));  // bypass the null stuff
                        } else if (target == typeof(int?))
                        {
                            o2 = Convert.ChangeType(o, typeof (int)); 
                        } else if (target == typeof(DateTime?))
                        {
                            o2 = Convert.ChangeType(o, typeof (DateTime));
                        }
                        else
                        {
                            // this generic conversion seems to catch most everything
                            o2 = Convert.ChangeType(o, target);
                        }
                        setter.Invoke(item, new object[] {o2});

                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Need to handle from type: {0}, value:{1} to setter type: {2}", source.FullName, o.ToString(), target.FullName);
                    }
                }
                result.Add(item);
            }
            return result; 
        }

        public static void UpdateListToSheet<T>(this Worksheet sheet, IEnumerable<T> lists, string pkColumnName)
        {
            pkColumnName = pkColumnName.Trim().ToLower();

            // Grab the list of columns
            var nameToCol = GetNameToColumnDictionary(sheet);

            // get a mapping from list member to column number / vice versa
            var colToGetter = GetColToGetterDictionary<T>(nameToCol);


            // grab the mapping of pk to row number
            if (!nameToCol.ContainsKey(pkColumnName)) throw new Exception("Could not locate primary key " + pkColumnName + " on sheet " + sheet.Name);
            var pkColumnNumber = nameToCol[pkColumnName];
            var pkToRow = new Dictionary<string, int>();
            var emptyRows = new List<int>();
            for (var r = 2; r <= sheet.UsedRange.Rows.Count; r++)
            {
                var pkValue = sheet.Cells[r, nameToCol[pkColumnName]].Value;

                if (pkValue == null)
                {
                    emptyRows.Add(r);
                }

                var test = pkValue.ToString();
                if (test == String.Empty) continue;
                pkToRow[test] = r;
            }

            // get a direct line to the getter of the pk
            if (!colToGetter.ContainsKey(pkColumnNumber)) throw new Exception("Could not locate primary column in DTO????");
            MethodInfo pkGetter = colToGetter[pkColumnNumber];

            // write the list contents into matching column names in the spreadsheet, updating as we go
            foreach (var item in lists)
            {
                var pkVal = pkGetter.Invoke(item, null).ToString();
                if (String.IsNullOrEmpty(pkVal)) continue; // inbound item has no PK

                // figure out which row to write to
                int rowToUpdate = -1;
                if (pkToRow.ContainsKey(pkVal))
                {
                    // row already exists .. update it
                    rowToUpdate = pkToRow[pkVal];
                }
                else
                {
                    // row does not exist .. append it
                    if (emptyRows.Count > 0)
                    {
                        rowToUpdate = emptyRows[0];
                        emptyRows.RemoveAt(0);
                    }
                    else
                    {
                        rowToUpdate = sheet.UsedRange.Rows.Count + 1;
                    }
                }

                // write it out
                foreach (var e in colToGetter)
                {
                    int col = e.Key;
                    MethodInfo getter = e.Value;
                    var o = getter.Invoke(item, null);
                    // any translations from .Net to Excel would happen here - none so far
                    sheet.Cells[rowToUpdate, col] = o;
                }
            }
        }

        #region Private

        private static Dictionary<string, int> GetNameToColumnDictionary(Worksheet sheet)
        {
            var nameToCol = new Dictionary<string, int>();
            for (var c = 1; c <= sheet.UsedRange.Columns.Count; c++)
            {
                var columnName = sheet.Cells[1, c].Value;
                if (!(columnName is string)) continue;
                if (String.IsNullOrEmpty(columnName)) continue;
                columnName = columnName.Trim().ToLower();
                nameToCol[columnName] = c;
            }
            return nameToCol;
        }

        private static Dictionary<int, MethodInfo> GetColToGetterDictionary<T>(Dictionary<string, int> nameToCol)
        {
            var colToGetter = new Dictionary<int, MethodInfo>();
            var properties = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var p in properties)
            {
                if (!p.CanWrite || !p.CanRead) continue;
                MethodInfo mget = p.GetGetMethod(false);
                if (mget == null) continue;
                var lowerPropName = p.Name.ToLower();
                if (!nameToCol.ContainsKey(lowerPropName)) continue; // not present in target xls
                colToGetter[nameToCol[lowerPropName]] = mget;
            }
            return colToGetter;
        }

        private static Dictionary<int, MethodInfo> GetColToSetterDictionary<T>(Dictionary<string, int> nameToCol)
        {
            var colToGetter = new Dictionary<int, MethodInfo>();
            var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var p in properties)
            {
                if (!p.CanWrite || !p.CanRead) continue;
                MethodInfo mset = p.GetSetMethod(false);
                if (mset == null) continue;
                var lowerPropName = p.Name.ToLower();
                if (!nameToCol.ContainsKey(lowerPropName)) continue; // not present in target xls
                colToGetter[nameToCol[lowerPropName]] = mset;
            }
            return colToGetter;
        }
  • Not yet memory-leak proof
  • Can only use a long as an id
  • Does not handle nullable columns gracefully

I’ll post more later on what I was trying to do.   I did finally get it to work.   Sketchy as it all is.  I love that word Sketchy… thank you Rutledge Wood!

Now I have to go pick up my race bib for a 10 mile race tomorrow. 

Three Sunset Timelapses of Louisville Downtown

I found an app, The Photographer’s Ephemeris, and in playing with it I figured out that I could predict when the Sun would line up with certain landmarks.   So I decided to try it with Downtown Louisville.  I set a reminder to myself.

And promptly forgot to bring my Video Camera in that day.  It was Friday 3/29/2013, and the sunset was gorgeous.

No problem, I’ll just catch it the next day.

Saturday 3/30 – Overcast and hazy.  That’s #3 in the video.

Sunday 3/31 – Beautiful Sunset.  I skipped part of Louisville NCAA Madness to set this up.  User error – it didn’t record.

Monday 4/1 – Pretty good.  That’s #1 in the video.  Pulled back from my first attempt.   Every 5 seconds

Tuesday 4/2 – hazy, skipped.

Wednesday 4/3 – Not quite as good.  #2 in the video.  Pulled even further back.   Every 2 seconds, but I sped it up for the video.

[youtube=http://www.youtube.com/watch?v=qpFvINjSK_I&w=448&h=252&hd=1]
Downtown Sunset Timelapse

The video is in 1080, so fullscreen is best.

By this point, the sun had slipped away from being lined up with downtown; and I had figured out that the stock zoom lens on my camcorder does not make for a great picture in low light conditions.    So ends that project; I’ll resume from a Tripod on the Pedestrian Bridge later in the summer… sun reflecting off the buildings.

Sunny’s Crash Course on FL-Studio

a. I have been very impressed by the “Crash Course” channel on YouTube – http://www.youtube.com/user/crashcourse

b. When I first started using FL-Studio, most videos were either too slow, too vulgar, or not broad enough.  So, once I learned to use the system, I felt the need to make my own crash course video on the subject.

c.  After two takes and a lot of editing, I think its “good enough”.

d. If I could make my living at making instructional things fro people who wanted to learn them, wow, sign me up.

Here it is:

[youtube http://www.youtube.com/watch?v=OoGCbciUQ0c&w=560&h=315]

I did learn while doing this:

1. Its better to talk and demo at the same time, rather than reading off a script and then trying to match the demo to the script later.

2. Youtube “links” to things can only go to other Youtube or Google things.   So, I couldn’t link to a wikipedia article, for example.

3. I’m an engineer, not an artist.

Picking up Running again–analysis of Heart Rate vs Speed

In 2010, I started running.  I started at a max distance of 0.25 miles; ran into shin-splints; did a bunch of research, bought some Vibram Five Fingers, and ran my first 5k in 36 minutes or so.   I (too) rapidly progressed to running the Bourbon Chase that year, and in 2011, I competed in the Louisville Triple Crown of Running.  At the time, I was pretty well conditioned (for me). 

2012, nada.  I had a knee injury late in 2011, and didn’t do much of anything.

2013, I’m back in running form.. just barely.  I started a few weeks ago, I’m barely keeping up with the distance I need for the Triple Crown.   The 10k is tomorrow morning.

Geeky-ness

I have heart rate data from both years:

image

  • I have no idea what unit the speed is in. 
  • The Y Axis starts at 70, although for me it could start at 90.  If it were zero, I would be dead. Smile 
  • The Purple shows a training run (I think) from 2011 January, when I was running around 4-5 miles.
  • The Red shows me competing in the 2011 Rhodes Run (I think). Or, it was a long run.  You can see the significantly higher speed (to the right)
  • The Blue is my training run from last night.  I went REALLY slow – that’s probably 3-3.5, somewhere between 15 to 18 minutes per mile.

My take-away is that purple is shifted to the right of blue – I could go faster at the same heart rate, back then.

Here’s a slightly different view, courtesy of the free-compare tool at www.movescount.com – which came with my kick-ass Suunto t6d watch

image 

  • Orange are all the runs from January to March of 2011.
  • Black are all the runs from 2013 (I haven’t “associated” the workouts with running, so they are still black)
  • Once again, same heart-rate, less speed.   
  • I have some room for improvement.

There is a direct relationship between heart rate and speed. 

Heart Rate Training

According to the book “Total Heart Rate Training” there’s also markers of heart rate zones to be found.   Here’s my interpretation of it, augmented with data collection devices:

  • Walk for at least 15 minutes first, to get the “jigglies” out.  
  • Start slow, on a treadmill, but at least jogging, like 3.5 mph.   Your jogging speed could be slower than your fastest walking speed! 
  • Slowly speed it up.  Let yourself adjust to each new speed.  I do a count to 8 or 16 before adding another 0.1 mph. 
  • At some point, you will feel yourself start to breathe a bit harder.  You will still be able to talk, just not as easily.   (There is no telltale thing on the graph for this).  Call this point “A”. 
  • This will last for a while.
  • At some point, it will start to get much harder much quicker, and you won’t be able to talk anymore, and you’ll be sucking for air.    Welcome to Anaerobic?  Aerobic?  THE OTHER ONE, the zone where you have 5 minutes of energy left, use it, loose it, and you’re done.  Call this point “B”
  • Charting the heart rate vs speed, you should see a bend where you switch to the other zone.   Up till that point, everything should be “approximately” a line.   When you switch to the other zone, there will be a “bend” and you’ll get a new (line/curve).   

image

  • These are from two this year.  I had a beautiful sample from 2011, BUT, I didn’t have any distance measuring equipment, so no speed data, only HR.
  • I’ve added an elbow in blue (older), and orange (more recent), as to where I think my transition point “B” was.
    • Another sign of transition:  Your heart rate does not slow down, so no “wobbles”.  Just unpeels upwards.
  • You can see my transition point move up – that’s me getting heart-toned, or, well, shaking the cobwebs out.
  • You can also see the orange is just a bit to the right of the blue – better speed at the same heart rate.    Although I suspect I was getting bored, hence there’s very few data points for 28 to 30.
  • I’m pretty sure my “A” point is somewhere around 145. 

Anyway, the heart rate training book talks about this in depth.   My understanding:

To improve performance, you need to keep your heart rate UNDER that elbow of “B” but near the top of it.  This helps nudge it upwards, which gives you more range to go faster for longer.   And it will get your muscles accustomed (built up) to that kind of workload.

To improve endurance, you need to train with your heart rate closer to “A” (just north of it).  This also helps improve “B”, but then your (leg) muscles will need time to catch up.

Caveat:  I tried this out with a friend who is VERY much out of shape.    While Walking, he got to his A point.  As SOON as he started jogging, he crossed “B”.  He needed a lot more heart toning before he would be ready for jogging.  My suggestion to  him was:  Get to walking 4 miles comfortably, before you start trying to jog for one.  And then when you jog, jog slower than you walk. 

Double Caveat:  The other really awesome exercise for running if you can stand it, is the Hundred Up.  Helps with all the muscles and landing your feet.

On Transitioning Assets between Environments

I’ve previously talked about unit tests VS integration tests and various database-inclusive testing strategies.  I have recently been dealing with some pain on making sure everything needed for some code is moved between environments – a process fraught with human intervention and forgotten steps and mistakes and ambiguity. In an IM conversation with one really smart guy about this (Hi Josh!), an Idea was born.

Preliminary to the Idea

We’ve gotten pretty good at getting code between environments.  There’s all kinds of things in source control for branching, and merging.  There’s different flavors of branching, and different ways to package together code to send it places, my personal favorite is using a build server to do the actual deployment (thus documenting it). 

However, every customer I’ve worked with, struggles with how to get [the right] database changes into an environment. None of them think they struggle with it, but they all do.  (well, 3 of the last 4).  A lot more than developers struggle with getting code moved around.   The DBA’s are usually saying “all you have to do is X”, and the developers are saying “you want me to do WHAT?”, and they meet somewhere in the middle.

The standard idealist’s view of this world is: check your database into source control.  The tricky bit becomes: using which solution?  Visual Studio SQL Server Database ProjectsRedgate SQL Source Control? E/R Studio Repository? RoundhousE?   I have not yet met a client who does this; usually there is some really weird way that the description of what needs to change gets handed to the DBA, who then takes the changes the rest of the way.

To broaden the scope, the kinds of assets that I’ve seen companies struggle with:

  • Database schema changes
  • Database data changes
  • Configuration entries per environment
  • The right list of components being deployed per environment
  • All of the above when adding a new environment (essential for feature branching)

Target Audience

Folks working at small companies, where they usually have full access to the production server, might scoff at this; sorry guys you are not my current target audience. 

The target audience is folks working at large enterprise companies, that host their own software — usually those that have Sarbanes-Oxley compliance that states: NO DEVELOPER CAN TOUCH THE PROD BOXES, EVER.    [sidebar: maybe it doesn’t actually say that, but it’s the excuse given every time].  It’s a black box. 

The target audience is also NOT people writing sellable software, where hopefully 500 customers will run an install 500 times – in those environments, there’s usually a chunk of resource per release to get the installers right.   And a lot of VM’s to run installs on.   Over and over. 

Personal peeve: I ended up trying to communicate with other folks on what needs to happen, using a very limited vocabulary, often with edge cases that don’t fit their language.  Sometimes, the chasm between our experiences was even larger, for example, in production they had a web farm behind a certain kind of load balancer with a firewall, and in lower environments where I had access, they didn’t (too expensive, not in the the department’s budget).   But I certainly was not allowed to log in to those things to get a “feel” for them, and how to work with them; yet I was expected to to tell them exactly what they needed to get it running.    Personal solution:  Cookies.  Shared.

The Birth of the Idea:

At the previously salaried job, I was in charge of adding some stuff to a website so that it could communicate to a web service. This web service was in charge of taking uploaded documents and processing them further, then depositing the results in a shared folder. I added a Test<ReleaseName>.aspx page to that website, where it talked to the TestWebService.asmx page that sat next to the normal web service, and they talked about stuff like: 

  • What identity was the service running under?
  • Did the service have read/write/delete access to the drop folder?
  • could the service transfer a LAAAARGE file without dying over the network?

Within the website and the web service, I made sure to limit the request so that it would only “do work” if the sender was from the developer’s subnet, specifically excluding the subnet that the internet came in on (after they got through the firewall).   Everything not recognized was immediately 404’ed.

The result was I could walk over, hang out with the build/deploy guy (without touching his keyboard and breaking compliance), and have him browse to the test website – and it told him everything that was going on, especially what was broken. We spent a LONG time trying to get the identity to run as the right person with the right access to the right folders, and if I hadn’t built the test pages, it could have gotten really frustrating.   (Rather than BUILDGUY=>”no”=>DEVELOPER=>”try”=>PERMISSIONSGUYS=>”done”=>DEVELOPER=>”again?”=>BUILDGUY, it was [DEVELOPER+BUILDGUY]=>”try”=>PERMISSIONSGUYS=>”done”=>[DEVELOPER+BUILDGUY]=>”yay!”)

The Idea: Create Tests

Here’s a sample of the kinds of tests that I’m talking about:

  • Can I communicate to W1 web service correctly?
  • Does table T1 exist?
  • Does table T2 have an entry for E1,E2?
  • Does table T3 have a column C1?
  • Is there any data that matches this known pattern SQL1 which should no longer exist?
  • Does Table T4 have an index I1? 
  • Does stored procedure SP1 exist?
  • Does stored procedure SP1 have xyz in its definition?   (this could get fancier in your environment if you have stored procedure revision numbers; mostly trying to ensure that a stored procedure got updated)
  • Does any stored procedure have any references to XXX in their definition? (when dealing with the case where many stored procedures need to be modified because a table is going away)
  • Do at least N stored procedures have any references to XXX in their definition? (when dealing with multiple stored procedures being updated because something required got added to a table)
  • Can I read the configuration entry for E1?
  • Do I have {x/y/z} permission to network path N1?

The Proposal

  • To add a test/configuration channel to every component of what assets are required for that component to function
  • The structure could be like this (C# psuedo-code, written in notepad:)
  • public class Requirement { 
    	public string Name { get; set; }            // to help us all talk about the same requirement
    	public string ReleaseName { get; set; }     // in which release was this introduced
    	public DateTime CreationDate { get; set; }  // when was the requirement created? 
    	public string Description { get; set; }     // the "why" of the test, documentation of the asset needed or what role it plays
    }
    
    public class RequirementResult { 
        public Requirement Requirement { get; set; }  // which requirement are we talking about? 
    	public bool? Success {get; set; }  			  // was it successful?  null = don't know
    	public string Result { get; set; } 			  // the result of the test, what's broken, or what worked
    	public IEnumerable ChildResults { get; set; }   // sub-component results
    }
  • If a component depends on a child component, have it be able to query the child component’s test channel (example: web service calls to a web service in our control).
  • To ensure that this test/configuration channel is locked down by a simple, shared piece of code that determines if the test/configuration channel can run (on which the enterprise can sign off as a whole), and provides the structure of the test results.

Summary

I think it would be pretty useful.

It would get even more useful over time – with the system getting more documented with each additional release.  This is the kind of detailed documentation many folks would drool over – much more useful than the documentation provided by writing unit tests, which in my experience, only 10% of developers think are useful.

I think it would help all the folks involved – developers, DBA’s, Network guys, Permissions guys, Deployment guys – talk the same language and have the same set of tools to determine if the work was “done”.  Rather than being at odds with each other: “you need to communicate with me in MY language!” 

The next project I get to start from scratch, I’m going to try this.