.net has a new Logo now – and I don’t like it.

As unveiled during the PDC in October 2008
As unveiled during the PDC in October 2008

Today, Microsoft unveiled a new Logo for the .net Framework, “The Wave”. To quote Scott Barnes, “We needed a logo that was in sync with the key values that we want .NET to stand for: consistency, robustness and great user experiences.” and later “aligned with the portfolio of brands that .NET is most strongly aligned with: Silverlight, Visual Studio and the AppPlat server products.”

Old Win2000-Style Logo
Old Win2000-Style Logo

To give my summary right away: I don’t like the logo. I think it’s boring, because it lacks attitude, personality, charm. I fail to see how this represents robustness and great user experiences. But I don’t want to turn this into an overly critic post. Let’s have a look at the old .net Logo for a moment. Wow. That was… bad. To be fair, it was in line with Microsofts branding at the time, if you look at Windows ME or Windows 2000 and associated Server Products. But that clearly looks dated now, 8 years later. Not many people seem to know the logo, and I can understand why. The .net Framework surely needed a new logo.

So let’s look at some of the other logos Microsoft came up with.

Surface
Surface
Silverlight
Silverlight
Visual Studio
Visual Studio
Expression
Expression

Each one of these logos has their own sort of dynamic. Each one of them has attitude. The Expression Mandalas are a quite extreme way of branding, but that makes them somewhat unique. Visual Studio is still one of my favorite Logos for any Product by any Company, ever. I mean, it’s INFINITY, but also with Gradients and colors to not only make the logo more interesting, but also to carry a message: Integrating multiple components (Languages) into one product that allows you to use everything to create one infinite stream. That’s cool. Surface is also almost equally good at carrying a message. On one side, it’s dynamic and natural due to the curves that represent a trails that your finger makes when using it. But it is also shaped like a human, to emphasize on the fact that it’s a human-centric product. Silverlight on the other hand has no message visible at first, but it is a quite abstract and attractive “smoke cloud” that at least makes it look non-generic.

Now, the new .net Logo surely has some message as well. They are multiple layers, all going in the same direction. That could be seen as the message of consistency among .net Languages. Also, the multiple layers look like a “reinforced” Wave, which could point out robustness. But then, the sharp corners make it look like it’s not very dynamic and it emphasizes that there are some really hard limits on the edges that prevent the wave from fully expanding. I mean: Sure, the .net Framework HAS limitations, and that is not meant as a negative things. It really shines in some areas (Web and Application Development) and is not really that great or usable in other areas (Game and Operating System development, despite XNA and Singularity). But should the logo really emphasize these limits instead of purely focusing on the strengths?

Also, I think the logo is a bit too “heavy”. If you look at Surface, Silverlight and Expressionl, you see that they are very lightweight due to use of transparency and bright colors. The Visual Studio logo is a bit more heavy, but uses very vibrant colors. The new .net Logo on the other hand uses this very dark blue as it’s center color, which makes the lighter shades on the edges not really visible.

Maybe it’s just because the logo is new and unknown that I am a bit sceptic, but the other logos were usually Love at first sight. Well, I think I have to try to get a Business Card once Scott Hanselman got new ones to see how it looks in the Wild ๐Ÿ™‚

Disclaimer: I am not a Designer, I am NOT able to create a better logo, so don’t bother asking, and this post was subjective and not meant to offend the designers and the people who like the logo.

PermanentRedirectResult – a 301 HTTP Redirect for ASP.net MVC

For a little project to learn ASP.net MVC, I needed my Controller to return a HTTP 301 Redirect. Unfortunately, just calling “return Redirect(url)” returns a HTTP 302 instead of a 301. After checking on the MVC Forums, there seems to be no “official” way to perform a 301 Redirect in a Controller. Of course, you can always modify the Response directly, but I decided that returning an ActionResult would be cleaner (and I think that makes it easier to unit test), even though it is essentially doing exactly that, setting a custom Status Code and Redirect Location.

So here is my little PermanentRedirectResult:

public class PermanentRedirectResult : ActionResult
{
    public string Url { get; set; }

    public PermanentRedirectResult(string url)
    {
        if (string.IsNullOrEmpty(url))
        {
            throw new ArgumentException("url is null or empty", "url");
        }
        this.Url = url;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        context.HttpContext.Response.StatusCode = 301;
        context.HttpContext.Response.RedirectLocation = Url;
    }
}

Base36 Encoder/Decoder in C#

Edit: An updated Version (which also adds BigInteger compatibility) is part of my Utility Library on GitHub: https://github.com/mstum/mstum.utils

For a project I am working on I needed a way to convert long numbers into Base 36 numbers. Base 36 uses number 0 to 9 and letters a to z, but is not case sensitive (there is no differentiation between uppercase and lowercase letters), which makes it perfect for being transferred over the telephone.

As the .net Framework does not have a Built-In function for Base 36, I’ve converted the one from the Wikipedia Article to C#. Note that the “Reverse” function could as well be an extension Method if you use a more recent .net Framework, but to keep it simple it’s an instance method here. You can test it against the examples in the Wikipedia article if you want to verify it.

By the way, I’ve chosen lowercase letters here, but if you want, you can just make them uppercase. Also, note that these functions work with a long, not with a double, so no floating point action here.

// Edit: Slightly updated on 2011-03-29

/// <summary>
/// A Base36 De- and Encoder
/// </summary>
public static class Base36
{
	private const string CharList = "0123456789abcdefghijklmnopqrstuvwxyz";

	/// <summary>
	/// Encode the given number into a Base36 string
	/// </summary>
	/// <param name="input"></param>
	/// <returns></returns>
	public static String Encode(long input)
	{
		if (input < 0) throw new ArgumentOutOfRangeException("input", input, "input cannot be negative");

		char[] clistarr = CharList.ToCharArray();
		var result = new Stack<char>();
		while (input != 0)
		{
			result.Push(clistarr[input % 36]);
			input /= 36;
		}
		return new string(result.ToArray());
	}

	/// <summary>
	/// Decode the Base36 Encoded string into a number
	/// </summary>
	/// <param name="input"></param>
	/// <returns></returns>
	public static Int64 Decode(string input)
	{
		var reversed = input.ToLower().Reverse();
		long result = 0;
		int pos = 0;
		foreach (char c in reversed)
		{
			result += CharList.IndexOf(c) * (long)Math.Pow(36, pos);
			pos++;
		}
		return result;
	}
}

Escaping Code for posting on the WebSite, and removing indent

In my postings, I now tend to use Code Snippets more. Along with the new Blog Theme, I also added Syntax Highlightinh using google-code-prettify, which produces really nice results, but has 2 drawbacks.

The first one is a drawback of the script itself: < has to be replaced with &lt; in all cases. The second problem is a problem with copy/pasting snippets: They tend to have an indent on the left that makes them unneccessarily wide. If I copy/paste something 4 levels deep, I have 16 spaces on the left. I wrote myself a simple tool that essentially does two things: It replaces all opening brackets with HTML &lt;, and it removes the left indent without breaking any further indentation. The function is really simple, maybe not the most efficient but good enough:

private void btnEscape_Click(object sender, EventArgs e)
{
    string classname = “prettyprint”;
    if (cbLang.Checked)
    {
        classname += ” lang-” + cbLangSelection.SelectedItem.ToString();
    }

    if (cbIndent.Checked)
    {
        int removeIndent = 0;
        foreach (string s in tbCode.Lines)
        {
            if (string.IsNullOrEmpty(s)) continue;
            int tmp = 0;
            foreach (char c in s)
            {
                if (c != ‘ ‘)
                {
                    break;
                }
                else
                {
                    tmp++;
                }
            }
            if (removeIndent == 0 || tmp < removeIndent)
            {
                removeIndent = tmp;
            }
        }
        StringBuilder sb = new StringBuilder();
        foreach (string s in tbCode.Lines)
        {
            sb.AppendLine(s.Length >= removeIndent ? s.Remove(0, removeIndent) : s);
        }
        tbCode.Text = sb.ToString();
    }

    tbCode.Text = string.Format(“<pre class=\”{0}\”>{1}{2}</pre>”, classname, Environment.NewLine, tbCode.Text.Replace(“<“, “&lt;”));
}

The application has a Button (btnEscape) and a Multiline Textbox (tbCode). Also, there is a Checkbox cbLang with a ComboBox cbLangSelection, and finally a second CheckBox cbIndent.

Now, the cbLang stuff is just for explicitely specifying the language the code is in for prettify.js to work. Usually, it tries to guess the language and does that quite good, but sometimes you may want to be explicit about it. Have a look at the FAQ for valid languages. The check is simple: If Checkbox clicked, append “lang-{0}” to classname.

Then, we got the indent-function. This checks every line and then every char in every line to find the length of the shortest indentation. Then, it builds a new string where it replaces the first characters up to removeIndent characters. Note that it ignores empty strings to not break on empty lines in the code.

The last function then just replaces the code in the Textbox with a pre-tag. Notice the tbCode.Text.Replace bit.

PS: If you want to add support for CTRL+A to select all text in the TextBox, use the KeyDown Event:

private void tbCode_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Control && e.KeyCode == Keys.A)
    {
        tbCode.SelectAll();
        e.Handled = true;
        e.SuppressKeyPress = true;
    }
}

Making the Songlist sortable by using IComparer and IComparable

Yesterday we’ve looked at a way to access iTunes from C# to get a list of recently played tracks. Today, we want to extend this solution a little bit, by making the list sortable in any way we’d like.

If you have a look at the sidebar, you will find a “Recently Played Songs” near the bottom. Achieving this is the ultimate goal of this small article series, and I’ll walk you through the steps needed to achieve this automatically.

The Series of Articles will include:

  1. Grab the contents of a Playlist from iTunes
  2. Making the Songlist sortable by using IComparer and IComparable
  3. Create a XML File
  4. Upload this file via FTP to this Server
  5. Write a PHP Script that performs an XSL Transformation (XSLT) on the Xml File and Call this file from Sidebar.php within WordPress

Of course, the last step applies to PHP and WordPress, but if you have an ASP.net, a Ruby on Rails, a Python or whatever Web Server, you can still follow the base concepts if you’d like.

Let’s get started. Yesterday, we created a Class called “Song” which essentially represents all the data about a song that we’re interested in and a custom Comparator to be able to sort it. Now, we extend this class a little:

[Serializable()]
public class Song : IComparable<Song>
{
    public string Title { get; set; }
    public string Artist { get; set; }
    public string Album { get; set; }
    public int Year { get; set; }
    public int Length { get; set; }
    public int PlayCount { get; set; }
    public DateTime LastPlayed { get; set; }

    public string LengthAsString
    {
        get
        {
            TimeSpan ts = TimeSpan.FromSeconds(this.Length);
            if (ts.Hours > 0)
            {
                return string.Format("{0}:{1:00}:{2:00}", ts.Hours,
                                              ts.Minutes, ts.Seconds);
            }
            else
            {
                return string.Format("{0:00}:{1:00}", ts.Minutes, ts.Seconds);
            }
        }
    }

    public override string ToString()
    {
        return
        string.Format("{0} - {1} ({2}, {3}) [{4}] {{Last Played: {5:dd/MM/yyyy HH:mm}, Total: {6}}}"
        , Artist, Title, Album, Year, LengthAsString,LastPlayed,PlayCount);
    }

    #region IComparable<Song> Members

    public int CompareTo(Song other)
    {
        return this.LastPlayed.CompareTo(other.LastPlayed);
    }
    #endregion

    public int CompareTo(Song other, SongComparer.SortField field, bool sortDescending)
    {
        int result = 0;
        switch (field)
        {
            case SongComparer.SortField.Album:
                result = this.Album.CompareTo(other.Album);
                break;
            case SongComparer.SortField.Artist:
                result = this.Album.CompareTo(other.Artist);
                break;
            case SongComparer.SortField.Title:
                result = this.Title.CompareTo(other.Title);
                break;
            case SongComparer.SortField.Year:
                result = this.Year.CompareTo(other.Year);
                break;
            case SongComparer.SortField.PlayCount:
                result = this.PlayCount.CompareTo(other.PlayCount);
                break;
            case SongComparer.SortField.Length:
                result = this.Length.CompareTo(other.Length);
                break;
            case SongComparer.SortField.LastPlayed:
                result = this.LastPlayed.CompareTo(other.LastPlayed);
                break;
        }
        if (sortDescending) result = result * -1;
        return result;
    }
}

Compared to yesterfay, the CompareTo function was modified and overloaded. Yesterday, the function was always trying to sort by LastPlayed date, descending. Now, I have added an Overload which takes two additional parameters to determine a field to sort by and whether or not to sort in descending order. Also note that we are not coding the actual comparison logic ourselfes. Yesterday i’ve done it to illustrate the 3 return values, but here we are just calling the CompareTo-function that many .net Types already implement, hence no need for extra logic on our side.

For our Overload to work, we need to create a class that implements IComparer. Notice the difference between IComparable and IComparer. IComparable indicates that the Object which implements it can be compared to other objects of it’s type, whereas IComparer is a “stand-alone” class that can be used against different objects. Also have a look at two blog articles about IComparable and IComparer. So here is a new class, the SongComparer:

public class SongComparer : IComparer<Song>
{
    public enum SortField
    {
        Title,
        Artist,
        Album,
        Year,
        Length,
        PlayCount,
        LastPlayed
    }

    public SortField Field { get; set; }
    public bool SortDescending { get; set; }

    public SongComparer()
    {
        SortDescending = true;
        Field = SortField.LastPlayed;
    }

    #region IComparer<Song> Members

    public int Compare(Song x, Song y)
    {
        return x.CompareTo(y, Field, SortDescending);
    }

    #endregion
}

This class starts out with an enum that defines the various fields that one can sort by. Now, using an enum is simple but if you add a field to the Song class, you have to extend the enum as well. There are other solutions to that (i.e. using Reflections), but again, for the sake of simplicity an enum works. We have then defined a class property to hold the selected field, and a boolean that indicates whether or not we want to sort descending. Our constructor just sets some default values (LastPlayed, descending). The important function is the Compare function. This takes two objects, and is then supposed to return one of the 3 return values that IComparable.CompareTo also returns. So in this case, it calls our overloaded Song.CompareTo() function. If you have a look at this function again, you see that it will now just look at the field and if the sort should be descending, it will just invert the result (through * -1).

The concept of IComparable and IComparer is quite simple, yet a bit tricky to grasp at first. The biggest issue is the compare order: You always compare the first object to the second object (first.CompareTo(second) or this.CompareTo(other)), otherwise the result will be in the wrong order.

So now that we have our custom IComparer, how do we use it? You can create an instance of the SortComparer class and then just pass it into List.Sort():

comparer = new SongComparer();
comparer.Field = SongComparer.SortField.PlayCount;
comparer.SortDescending = true;
List<Song> result = new List<Song>();
// fill the List...
result.Sort(comparer);

The List will now be sorted descending by PlayCount instead of LastPlayed. So now that we have made the Song class generic enough to sort by whatever field we like, the next step is to turn it into Xml. We will tackle that in an upcoming blog post.

Making SyntaxHighlighter XHTML 1.0 Strict compatible

As you may have seen, I have a new Theme now ๐Ÿ™‚ Along with the Theme, I also looked at ways to add proper Syntax highlighting. After a little conversation at Twitter with srijken, I had a look at the SyntaxHighlighter that is being used by Scott Hanselman as well.

Now, the result is quite good, as you can see in some earlier posts. Unfortunately, it breaks XHTML compatibility because it requires a name-attribute on pre-tags, which is non-standard. Well, I do not want to start a discussion whether or not a page should validate, but as this is my only validation error I’ve decided to fix it.

Essentially, my pre-tags now use the “lang” instead of the “name” attribute. There is a slight validation error here, because lang is supposed to specify a valid language code like “de” or “fr” for German and French, and using it now with “nonsense” values may be an accessibility problem. However, I am not aware of any case where this would cause a problem when used in a pre-tag, so I decided to use it.

The change to make is in shCore.js, where you change this function:

	function FindTagsByName(list, name, tagName)
	{
		var tags = document.getElementsByTagName(tagName);
		for(var i = 0; i < tags.length; i++)
			if(tags[i].getAttribute('name') == name)
				list.push(tags[i]);
	}

See the getAttribute call? Change ‘name’ to ‘lang’ and you’re done:

			if(tags[i].getAttribute('lang') == name)

Of course, you now have to change all your existing pre-tags, but well, being standard-conform is not always easy ๐Ÿ™‚

Update: The SyntaxHighlighter seems not to highlight JavaScript. I switched to google-code-prettify now, which has the advantage of supporting JavaScript for Highlighting and a bit easier integration. It’s used on StackOverflow and I like the output it produces, even though I recommend adding a “overflow: auto;” to the CSS file inside the prettyprint class.

Reading iTunes Playlists using C#

Here is a quick project that I’ve chucked together in the last hour or so. Essentially, I’ve written a little program that reads a playlist from iTunes. The plan is to have a sidebar that shows the “Most played” and “Recently played” Lists, but more on that in future posts.

Now, let’s start out simple. I have created a new Class Library named “iTunesStuff”, and in this I have created a new Class “Song”:

[Serializable()]
public class Song : IComparable<Song>
{
  public string Title { get; set; }
  public string Artist { get; set; }
  public string Album { get; set; }
  public int Year { get; set; }
  public int Length { get; set; }
  public int PlayCount { get; set; }
  public DateTime LastPlayed { get; set; }
  public string LengthAsString
  {
    get
    {
      TimeSpan ts = TimeSpan.FromSeconds(this.Length);
      if (ts.Hours > 0)
      {
        return string.Format("{0}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds);
      }
      else
      {
        return string.Format("{0:00}:{1:00}", ts.Minutes, ts.Seconds);
      }
    }
  }
  public override string ToString()
  {
    return string.Format("{0} - {1} ({2}, {3}) [{4}] {{Last Played: {5:dd/MM/yyyy HH:mm}, Total: {6}}}", Artist, Title, Album, Year, LengthAsString, LastPlayed, PlayCount);
  }
  #region IComparable<Song> Members
  public int CompareTo(Song other)
  {
    if (other.LastPlayed > this.LastPlayed) return 1;
    else
      if (other.LastPlayed < this.LastPlayed) return -1;
      else
        return 0;
  }
  #endregion
}

Nothing really spectacular here: The class implements some fields that describe a Song. The only interesting part is the IComparable Function. I want my list to be sorted by “Last Played Date”. Therefore I have implemented a custom Compare-Function that will compare the LastPlayed Field. For more information, see here. The purpose is to have a List<Song> and use Sort() on that List.

Now, we need a function that reads the information from iTunes. Apple provides a COM Interface that makes this task quite easy, you can simply Add a Reference to iTunes.exe (“iTunes 1.x Type Library”) and “using iTunesLib”. I have written a PlaylistReader Class for this purpose:

public class PlaylistReader
{
  public List<Song> GetPlaylistSongs(string playlistName, int limit)
  {
    List<Song> result = new List<Song>();
    iTunesAppClass ita = new iTunesAppClass();
    IITPlaylistCollection playlists = ita.LibrarySource.Playlists;
    for (int i = 1; i < playlists.Count+1; i++) // iTunes starts counting from 1
    {
      if (playlists[i].Name.Equals(playlistName, StringComparison.OrdinalIgnoreCase))
      {
        IITTrackCollection tracks = playlists[i].Tracks;
        for (int j = 1; j < tracks.Count+1; j++)
        {
          if (tracks[j].Kind != ITTrackKind.ITTrackKindFile)
          {
            continue; // To filter out Web Radio URLs etc.
          }
          Song s = new Song();
          s.Title = tracks[j].Name;
          s.Album = tracks[j].Album;
          s.Artist = tracks[j].Artist;
          s.Year = tracks[j].Year;
          s.Length = tracks[j].Duration;
          s.PlayCount = tracks[j].PlayedCount;
          s.LastPlayed = tracks[j].PlayedDate;
          result.Add(s);
        }
        break;
      }
    }
    playlists = null;
    ita = null;
    result.Sort();
    if (limit > 0 && result.Count > limit)
    {
      result.RemoveRange(limit, result.Count - limit);
    }
    return result;
  }
}

Note that iTunes (or is it COM in General?) starts it’s collections from 1 instead of 0, so trying to access playlists[0] gives you an error. Also note that the order of tracks is not necessarily the current order in your iTunes. For example, the “Recently played” Smart Playlist is ordered by Last Played, but the COM Interface gives you back the tracks in the order they were added to iTunes. Hence I added the call to result.Sort() at the end. This will in turn trigger my custom CompareTo function in the Song Class, which means that the list will be ordered by “Last Played”, descending. The limit Parameter is just there to avoid returning a 300+ tracks list, which can be quite ugly on a Web Site…

The usage is then really straight forward:

private void button1_Click(object sender, EventArgs e)
{
  PlaylistReader rpl = new PlaylistReader();
  List<Song> songs = rpl.GetPlaylistSongs("Recently Played",25);
  StringBuilder sb = new StringBuilder();
  for(int i = 0; i<songs.Count; i++)
  {
    sb.AppendFormat("{0}: ",i+1);
    sb.AppendLine(songs[i].ToString());
  }
  textBox1.Text = sb.ToString();
}

Voilรก, huge success:


In the next post, I’ll try to get a custom IComperator up and running, so that we can sort the list by other criterias. I’ll also export the list to a file which I then parse on the sidebar of this blog, at least that’s the actual goal of this.

Just making stuff work

I consider myself still a rather mediocre developer. While I am developing since almost 20 years now (starting on the C-64 with Basic, moving to Pascal and Delphi during the 90’s up to PHP and C# in the past years), I never had much formal education or worked in a big development team with many senior developers to really teach some lessons. This has both advantages and disadvantages.

I switched over to C# a bit more than a year ago, and this was the first time I started working on really “big” projects, at least compared to what I’ve done before. Also, I started reading more and more about software development practices, especially in the agile world. Of course, if you read a lot of blogs and forums, everyone seems always to be talking about best practices, how stuff SHOULD be done, how to make it shiny, how to make it dance and sing. This is certainly good stuff, because after all, we all want to learn how to improve our work, do we?

Unfortunately, it sometimes leads to too much desire for perfectionism, up to the point where the project simply does not ship. This is also the side effect of the “not being able to let my baby go” behavior, where developers simply do not feel comfortable letting their program free because they feel it’s still unpolished and “just not ready yet”. I am in that camp at the moment as well. I have read so much about architecture, I would just love to start bringing in Continuous Integration with dedicated build servers, unit tests, mocking frameworks, automatic documentation generation and deployment into my process, all at the same time while building the best Web 2.0 AJAXy interface ever. Just: That’s not going to work. Rome was not built in a day, but the first versions from Rome were not perfect either.

I am currently trying to move a bit in the opposite direction, in the “Just damn ship it already!!!” direction. Now, Alex Papadimoulis certainly had a few excellent points in his “What Could Possibly Be Worse Than Failure?” Article, but it leaves out one point: A system can be partially rewritten after it is released. Now, rewriting sounds like a terrible idea and people love to point out the Netscape failure as a prime example not to do it. But that’s only half of the story. Netscape did tabula rasa and started from scratch again. This is something I would not recommend. But there is in my opinion nothing really wrong with taking one part/module of the system and rewriting it, based on the feedback you received.

Jeff Atwood is on Episode #134 of Hanselminutes, and some of his remarks seemed quite shocking for Scott. For example, Jeff has SQL Server installed on the Web Server, and also has the current development version of the website on the same server. My first thought was “WTF???”, but after thinking a bit more about it, I now think: “Why not?”. Of course, the proper way would be using at least 3 servers, a big firewall around the SQL Server and a separate Development Server. Well, nice idea, but we are now talking about 3 servers that will have to be paid every month, that require regular maintenance (updated and stuff) and that open up new challenges (i.e. If SQL Server is on a separate box, you are now transferring the data over a network, which may have worse performance than having the SQL on the Web Server and have everything simply go through the RAM). Is StackOverflow.com a good example of “great architecture”? Probably not. But is it an example of a Website that just seems to work despite not-perfect architecture? Probably yes.

Reddit was completely rewritten already, EVE Online just completely rewrote their network stack, and Mozilla had to split their product into separate ones (Firefox and Thunderbird) because it was just a big unmaintainable behemoth before.

So yes, it is easy to criticize people who are using the “wrong” architecture or not following all the best practices. It is easy to write a spec that requires 11 servers connected with 10 Gigabit low-latency infiniband network to describe a program that will revolutionize the Internet. But actually implementing it is what matters, and usually, you have to sacrifice some shiny things in order to just get the product finished. You will have to release an “unfinished” product, but in the end, that will not matter. Two things matter: a) having an idea and b) actually implementing it. It’s step b that is the important one.

I am not good at that yet, but now that I’ve spent 2 years of focusing on “best practices”, I will now try to also focus on “making stuff work, fast”. Or, to quote Linus Torvalds, “Release early. Release often. And listen to your customers.”.

ย 

Disclaimer: This may not apply to shrink-wrapped software. Actually, I am pretty sure it does not. Also note that there are some things that should definitely be spot-on directly, for example security around user data.