Thought Flow

Technology and other things

Author: David

  • Resumes are bullshit

    Today, I was asked by a consultancy agency to provide a resume (or Curriculum Vitae or CV) with “some more meat on it” than one I had sent earlier which was very basic and with simple bullet points. I have always felt that resumes are bullshit and I have always wanted to blog about it so this was my cue to finally write down my thoughts on the subject. Here are a few reasons why resumes are bullshit:

    • Resumes tell what you worked with but not how.
    • Your former jobtitle as “Dynamic Creative Supervisor” sounds like it was created with the Bullshit Job Title generator (which indeed I just did).
    • A resume does not tell whether or not you can actually solve problems. Like the FizzBuzz problem. 1
    • Paula Bean was apparently able to get a job based on her resume. That was a bad idea.

    Resumes might be good for initial weeding out in applications but unfortunately this will often mean that inexperienced programmers do not stand a chance in the job race and I think that is a shame. I myself am a good example of a person with a thin CV when it comes to IT-related experience which is mainly due to the fact that I have spent most of my time at the university being a teaching assistant instead of having a job at an IT-company. But does this mean that my problem solving skills are worse than a person having worked for twenty years doing the same thing over and over? I think not.

    I do not deny that experience is important and Peter Norvig’s essay Teach Yourself Programming in Ten Years is a good reminder that programming is a skill that is only acquired with hard work over an extended period of time but putting so much emphasis on a piece of paper is sad. Is there no better general alternative?

    So after having dutifully updated my CV, I wrote this comment to the consulting agent (translated from Danish):

    In a time where one can buy diplomas online, improve on reality in one’s CV and in many ways get by in life by cheating, I think it is very unlucky that choosing employees is still based on something as untrustworthy as a piece of paper with a Latin title and some dates and job titles. I can find out more about a person’s personality in a 10-minute conversation than I can reading a personal description and I can learn more about a person’s competencies by posing a simple IT-related problem and have them solve it than I can by going through every former position the person has had.

    And here is his response (translated from Danish):

    I see what you’re saying about CVs but actually, I do not entirely agree with you.

    CVs are extremely important in our sales process because they decide whether we get the opportunity to come in and further discuss a job with the customer (they often have a number of CVs from different consultants to choose from). I get an enormous amount of bad CVs sent to me and my prejudice from seeing the CV is rarely different from when I actually meet the person later.

    Remember that CVs are not about improving on reality but presenting the material in a professional manner and with focus on the essentials…. This is something that is valid for sales in general.

    So the jury is up on the question but the title of this blog post remains the same. Meanwhile, I hope I didn’t scare him away :-)

    Update: Since writing this article, my views have been challenged from a lot of different people which is a good thing. However, there are plenty of articles to support my point. Here is a few:
    Forbes on resume lies
    Yahoo Finance on resume lies
    Job Bank USA on resume lies

  • Strange Linq-to-SQL performance when using Count

    Yesterday, I was writing a Linq-to-SQL query and noticed a quite remarkable difference in performance between two very similar queries (seconds versus minutes of running time). I thought it would be worth sharing.

    Basically, I have a bunch of orders that are represented by two SQL tables, called Basket and BasketItem. If an order e.g. consists of the purchase of items A, B and C, the corresponding rows in the tables could be:

    Basket:     ID = 1
    BasketItem: ID = 1   BasketID = 1   ItemID = A
    BasketItem: ID = 2   BasketID = 1   ItemID = B
    BasketItem: ID = 3   BasketID = 1   ItemID = C
    

    This is a fairly common way to represent order information in a relational database, establishing a one-to-many relationship between an order and its separate order lines.

    Now, somewhere in the code I am working on, I have an item set (for example { A, B }) and I want to figure out in how many orders these two items occur together. The items are represented in a list:

    // items is given as a parameter
    // but manually initialized here for descriptive purposes
    List<object> items = new List<object>() { "A", "B" };
    

    First version of the solution to the problem looked like this:

    int count = 0;
    foreach (Basket b in Baskets)
    {
      IEnumerable<BasketItem> bItems = b.GetBasketItems();
      if (items.All(item => bItems.Any(bItem => bItem.ItemID.Equals(item))))
        count++;
    }
    return count;
    

    If the BasketItems for a Basket contains all the given items, increment a count. This is obviously not optimal since it requires all BasketItems to be fetched from the database. After a lot of pondering and test, I came up with the following Linq query:

    int minLines = items.Count;
    var count =
      from basket in Baskets
      where
        (from basketItem in BasketItems
         where basketItem.BasketID == basket.BasketID &&
         items.Contains(basketItem.ItemID)
         select basketItem).Count() >= minLines
      select basket;
    return count.Count();
    

    If the query finds items.Count or more BasketItems for a given Basket, the basket is selected and the number of valid baskets are returned in the end. This dropped the running time from ~50 seconds to ~8 seconds for about 7000 calls to the method with the above code (for different items lists), a nice improvement.

    I then thought that the above Linq query could be further improved by using Count() directly instead of using Where clauses. It then rewrites to:

    var count =
      AnteconsBaskets.Count(basket =>
        AnteconsBasketItems.Count(basketItem =>
          basketItem.BasketID == basket.BasketID &&
          items.Contains(basketItem.ItemID)) >= minLines);
    return count;
    

    Using the above code, I had to manually halt the program after waiting more than 10 minutes for execution to complete.

    The question now is: Why did this code perform so poorly compared to the code in listing 2? I cannot offer an explanation of this but I can hint at it. The code in listing 2 translates into an SQL query where COUNT(*) is used in the sub-query for the BasketItem selection. For listing 3, Count() translates into an SQL query that uses a SELECT CASE WHEN query with three cases. Why this is so slow, I do not know but I will definitely not use Count() like this in the future.

  • Sorting an ASP.NET GridView with a custom data source

    The other day, I had to present some data in an ASP.NET GridView control and I had to allow sorting of some of the columns. This is easy if the data source is based on e.g. an SQL data source but not particularly easy when using a custom data source. In my case, I had a simple array of custom objects containing invoice data.

    There are many solutions for this problem on the web but none of them really seemed to do the trick in my case. Many of the solutions I could find rely on converting the data source into a DataTable object but this is not a good idea when sorting needs to be applied to many fields because the DataTable needs to have these fields added manually.

    So without further ado, here is a simple solution for the problem. First, let’s say our custom data source consists of an array of Person objects, defined like this:

    class Person {
      string Name { get; set; }
      DateTime BirthDay { get; set; }
    }
    

    The GridView to bind the persons to is very simple:

    <asp:GridView ID="PersonGridView" runat="server"
      AutoGenerateColumns="false"
      AllowSorting="true"
      OnSorting="personGridView_Sorting">
      <Columns>
        <asp:BoundField DataField="Name" SortExpression="Name" />
        <asp:BoundField DataField="BirthDay" SortExpression="BirthDay" />
      </Columns>
    </asp:GridView>
    

    The SortExpression has to be exactly the same name as the field. The sort direction and sort expression are stored in the ViewState and accessed by two fields in code-behind:

    private SortDirection CurrentSortDirection {
      get {
        if (ViewState["SortDirection"] == null)
          ViewState["SortDirection"] = SortDirection.Ascending;
        return (SortDirection)ViewState["SortDirection"];
      }
      set { ViewState["SortDirection"] = value; }
    }
    
    private string CurrentSortExpression {
      get {
        if (ViewState["SortExpression"] == null)
          ViewState["SortExpression"] = "Name";
        return (string)ViewState["SortExpression"];
      }
      set { ViewState["SortExpression"] = value; }
    }
    

    Notice that I have chosen to have Name sorted in ascending order by default. This is of course not necessary but convenient for later. Next up is the OnSorting event:

    protected void personGridView_Sorting(object sender, GridViewSortEventArgs e) {
      CurrentSortExpression = e.SortExpression;
    
      // Ignore e.SortDirection since it is always Ascending, an apparent bug in .NET
      if (CurrentSortDirection == SortDirection.Ascending)
        CurrentSortDirection = SortDirection.Descending;
      else
        CurrentSortDirection = SortDirection.Ascending;
    
      BindPersonData();
    }
    

    Finally, we can write our binding method.

    private void BindPersonData() {
      Person[] persons = GetPersonArrayFromSomewhere();
    
      // Sort the persons, if sorting is applied.
      if (!string.IsNullOrEmpty(CurrentSortExpression)) {
        System.Reflection.PropertyInfo personInfo = (typeof(Person)).GetProperty(CurrentSortExpression);
        if (CurrentSortDirection == SortDirection.Ascending) {
          persons = persons
            .OrderBy(person => personInfo.GetValue(person, null))
            .ToArray();
        }
        else {
          persons = persons
            .OrderByDescending(person => personInfo.GetValue(person, null))
            .ToArray();
        }
      }
    
      PersonGridView.DataSource = persons;
      PersonGridView.DataBind();
    }
    

    The sorting can also be applied with a different sorting method. I happen to like Linq so that is why I have used it here. The key thing to notice here is how the correct field for sorting is found with the use of the reflection framework:

    System.Reflection.PropertyInfo personInfo = (typeof(Person)).GetProperty(CurrentSortExpression);
    

    That is why it is essential that the SortExpression value in the GridView is the same as the field’s name.

    Oh and by the way, paging with a custom data source is very easy. Add this to your GridView:

    AllowPaging="true"
    OnPageIndexChanging="personGridView_PageIndexChanging"
    

    and this method to the code-behind:

    protected void personGridView_PageIndexChanging(object sender, GridViewPageEventArgs e) {
      PersonGridView.PageIndex = e.NewPageIndex;
      BindPersonData();
    }
    
  • Why I stopped drinking and started sleeping

    Alcohol
    Alcohol

    In July this year, just as I was starting up metacircle, I made a decision to stop drinking alcohol. Since then, I have become increasingly happy with this decision. What does this have to do with software and the IT industry? As it turns out, quite a lot — at least for me.

    First, I’m not trying to be “holy”, “saved”, “better than you” or anything else. I did my fair share of drinking earlier. Back in the days. Hopefully, those days are gone but my point is that I am not starting a crusade against alcohol.

    The short- and long-term effects of alcohol on the brain have often been debated but there seems to be no doubt that consuming even the slightest amount of alcohol is degenerative for the brain in some way. On top of that, sleeping under the influence of alcohol seems to impair the sleep pattern, although this is also a debatable subject [1][2].

    But a scientific study is one thing, the way you feel is another. My non-drinking experiment was primarily a test to see what would happen if I stopped since I had increasingly felt that the effects of a hangover sometimes carried over to every-day situations several days after a night out.

    And here is the result: For the last half year, I have been more aware and sharp in my head. I feel that I have an easier time understanding new problems and technologies and combined with the fortunate situation that I can sleep as much as I want (almost), I am currently in a situation that is much more personally satisfying than ever before. The IT industry is competitive, there is always someone smarter than yourself and knowing this is frustrating. But the frustration is reduced when the amount of deliberate, self-inflicted brain-degeneration is minimized.

    And actually, it is quite healthy for the rest of the body as well.

    References:
    [1] http://en.wikipedia.org/wiki/Alcohol_use_and_sleep
    [2] http://www.webmd.com/video/breus-drink-bed

    Added since original post:
    Sleep is more important than food
    Surprising Ways to Get a Better Night’s Sleep

  • Basic Http authentication from .NET to PHP webservice

    Today, I had a major problem that had a not-so-complex-but-rather-stupid solution so I thought it might be worth sharing since I only found online solutions that were rather old.

    I have remote access to a PHP webservice, written using NuSOAP. I call the methods of this webservice from a .NET application. This is no problem at all and I can easily call most webservice methods from within .NET. But some of the methods require basic authentication and now the trouble begins.

    One would think that it would be enough to just set the credentials for the webservice and make sure that it authenticates on every request. Like this:

    MyPhpWebservice ws = new MyPhpWebservice();
    ws.PreAuthenticate = true;
    CredentialCache cache = new CredentialCache();
    cache.Add(
       new Uri(ws.Url),
       "Basic", 
       new NetworkCredential(wsUsername, wsPassword)
    );
    ws.Credentials = cache;
    ws.CallSomeFunctionThatNeedsAuth();
    

    Alas, this did not work. In many places online, people say that .NET does not actually send the authentication details on the first request for some webservice function. If this was true, the solution would simply be:

    ws.CallSomeFunctionThatNeedsAuth(); // Does not auth
    ws.CallSomeFunctionThatNeedsAuth(); // Should auth now?
    

    This did not work either. Other people in other places online say that one needs to manually add the authentication details to the HttpWebRequest header. But the Visual Studio auto-created MyPhpWebservice class does not expose its WebRequest object so it cannot be directly altered. However, we can override the method GetWebRequest for the webservice and that way manually add the authentication header. This took me a while to figure out but here is a simple solution:

    class MyOverriddenPhpWebservice : MyPhpWebservice
    {
        protected override System.Net.WebRequest GetWebRequest(Uri uri)
        {
            HttpWebRequest request;
            request = (HttpWebRequest)base.GetWebRequest(uri);
    
            if (PreAuthenticate)
            {
                NetworkCredential cred =
                    Credentials.GetCredential(uri, "Basic");
                if (cred != null)
                {
                    byte[] credBuffer = new UTF8Encoding().GetBytes(
                        cred.UserName + ":" + cred.Password);
                    request.Headers["Authorization"] =
                        "Basic " + Convert.ToBase64String(credBuffer);
                }
            }
            return request;
        }
    }
    

    The code that I wrote in Listing 1 is now perfectly valid and all I need to do is exchange MyPhpWebservice with MyOverriddenPhpWebservice.

    And the best part: It works :-)