Software Archaeology Techniques

by James Shaw 6. November 2009 04:42

Listened to a good podcast on techniques reading other's code.  

Reminds of my last project of adding a feature to a partner company's C code with lots of dependencies on libraries tied to the OS (Linux), no tests, no debugger working, and no documentation :).

Key take-aways for me from the podcast:

1. Start small and look at 5-20K lines of code for practice (e.g., open source projects). 

2. Know the environment (e.g., read the source code of the Ruby interpreter if you're programming Ruby).  Beware of evil wizards (e.g., understand the code generated by IDE wizards).

3. Use version control to modify the code you're reading to play around with it.  Use VM for playing with code that's tied to the OS (like the project I had). 

Tags:

Tech

C# is more advanced than Java, but...

by James Shaw 7. October 2009 23:49
FYI: My comment to the blog post C# now a better language than Java:
 
Interesting discussion here.  I've had about 7 yrs programming in Windows/.Net and 3 yrs in Java experience, and I agree that C# as a language has more than caught up with Java, and become more advanced and convenient to use, esp Linq.  After using Linq for everyday tasks like filtering collections of objects or parsing XML, it's hard to go back to the "old" way of doing things; it's just more expressive and better for the VM too as language becomes more declarative for the CLR to optimize in the future (parallelize behind the scenes for example).  New features coming out in C# 4.0 like writing dynamic language directly in C# and integrating with the static part just shows that the MSFT folks are constantly improving the langauge given the trends.

 

On the other hand I also agree that JVM is much more portable and in use than CLR, and I hope that MSFT realizes the conflicting goals of having the most popular developer platform and tying companies to Windows.  After dabbling in both open source tools and MSFT tools, MSFT developer tools are indeed the best (Visual Studio beats out Eclipse by wide margin); it'll be a shame for MSFT to continue to keep those superior tools and languages behind the walls of Windows platform.  Visual Studio should've beeen cross-platform like MonoDevelop.  It would be interesting if in the future the Mono implementation becomes more popular than MSFT's implementation.  I totally salute the efforts of the Mono folks and think that MSFT should've been the one pushing for the effort and making the entire .Net more open and license free (not just the run-time and the language but all the frameworks as well).  

 

The macro trend is for technologies to be more open, so that businesses collaborate with a fraction of the effort on the technologies (e.g., Linux kernel) than if they were to implement the whole thing themselves (like what MSFT is doing), and also benefit from having a stake and say in the direction of the technologies rather than being dictated and tied to a specific vendor.  That's why Unix/Linux is so popular with businesses, from the small device (iPhone, Android, Palm Pre) to desktop (like Mac OS and Ubuntu) to server.

 

I have to check out Scala given your high opinion :).  I heard twitter rewrote their Ruby code to Scala for performance.  One thing I wonder about these other JVM language is performance, whether the community is able to make it keep up with Java (even though all compile down to Java byte code, I assume the compilation itself could have different efficiencies).  

Tags: , , , , ,

Tech

Running VirtualBox on a Windows Vista bootcamp partition in Mac OS X

by James Shaw 26. August 2009 11:51

VirtualBox is an open-source virtualization software from Sun much like VMWare and Parallels, but free for personal use!  I just tried it out on my Mac OS X to run a Windows Vista VM on a physical Windows Vista partition created through bootcamp, and thought I'd share my experience (and to write down for myself for future reference :)).  

I had googled and found someone doing very similar thing on a Ubuntu machine.  Most of the info is pretty relevant on a Mac OS X as well, except that I couldn't use the solution of assigning my user account to the "disk" group; there's no "disk" group on Mac OS X.  The other solution mentioned of changing permission works however.  Here's exactly what I did:

0. Read the relevant part of the VirtualBox User Manual regarding using raw partition ("9.10.2 Access to individual physical hard disk partitions").

1. Locate the device and partition holding your Windows Vista partition (e.g, using "df -k" command):

jshawsworlds-macbook:~ jshawsworld$ df -k
Filesystem    1024-blocks     Used Available Capacity  Mounted on
/dev/disk0s2     73269248 31999364  41013884    44%    /
devfs                 110      110         0   100%    /dev
fdesc                   1        1         0   100%    /dev
map -hosts              0        0         0   100%    /net
map auto_home           0        0         0   100%    /home
/dev/disk0s3     82685740 56175456  26510284    68%    /Volumes/Untitled
/dev/disk1s1        15632      144     15488     1%    /Volumes/CANON_DC
/dev/disk2s1    334784000 46614707 288169292    14%    /Volumes/My Book
/dev/disk2s2    153600000 29766376 123833624    20%    /Volumes/Mac OS Partition 2

For me, for example, I know the Windows partition is /dev/disk0s3.

2. Back up your existing Windows Vista partition, to a external drive for example, since you might damage the Windows paritition in this exercise and render it unusable!  One free tool I find pretty reliable in backing up and restoring Windows partition is winclone.

3. Change to root to do the next few commands:

sudo su

You may need to enable root user by going to the "Directory Utility" app, click on the lock icon, and then Edit->Enable Root User.

4. Unmount the Vista partition in Mac OS X if you currently have it mounted, since we don't want VirtualBox accessing it at the same time that Mac OS is accessing it:

umount /dev/disk0s3

and make sure it has been unmounted (i.e.,  the directory /Volumes/Untitled is empty in my case).

5. Change the device to read/write, which is needed by VirtualBox as stated in the user manual:

chmod 666 /dev/disk0*

Note that you have to change permission for all of disk0, not just the Windows parition (hence the "disk0*"), since createrawvmdk needs to access disk0 as well as disk0s3.

6. Run the command

VBoxManage internalcommands createrawvmdk -filename /Users/jshawsworld/WinVista.vmdk -rawdisk /dev/dev0 -partitions 3 -relative

7. Finally change the owner to your user account for the vmdk file you just created as root, so you can access it as yourself when running VirtualBox later:

chown jshawsworld /Users/jshawsworld/*.vmdk

8. Run VirtualBox from Finder, and browse to the vmdk file. 

9. Make sure that "IO APIC" and "VT-x/AMD-V" in your Virtual Machine Settings are both on, and then start the VM.  It should start.

Even though I got it working, it was painfully slow..., even after giving it 2 GB of memory.  I had tried VMWare Fusion to do the same thing before, and remember it wasn't any faster.  To really work efficiently in Windows I think booting to it via bootcamp is still the way to go.  XP might be faster; who knows maybe Windows 7 will be much faster.  For light-weight OS's (like Linux) running it as a VM is definitely the way to go.

Beautiful Code

by James Shaw 13. March 2009 22:07
When coding recently, I found myself greatly appreciating C#'s rich features that reduce the number of lines of code written.  Let me elaborate.

There has been so many times when I find myself wanting to access a list of objects randomly, based on some field of that object.  To do it efficiently I'd want to use a dictionary to to access these objects, with the key being the field I want to find the object by.  This need happens so often with lists of different types of object (say from 3rd party API's you use) that it's best to write a generic helper function that can wrap a list of any type of object with a dictionary and returns that dictionary, rather than writing a helper function for list of each type of object.  The following is such code, using generics and reflection in C#:
 
    public class Util
    {
        ...
        /// <summary>
        /// generic method that wraps the given IEnumerable into a IDictionary with the given
        /// field (of the objects in IEnumerable) as the key.
        /// </summary>
        /// <typeparam name="KeyType"></typeparam>
        /// <typeparam name="ValType"></typeparam>
        /// <param name="src"></param>
        /// <param name="keyFieldName"></param>
        /// <returns></returns>
        public static IDictionary<KeyType, ValType> ToDictionary<KeyType, ValType>(IEnumerable<ValType> src,
                                                                                   string keyFieldName) 
            where KeyType : class
            where ValType : class
        {
            var ret = new Dictionary<KeyType, ValType>(src.Count() );

            foreach (ValType ele in src)
            {
                var key = typeof(ValType).GetProperty(keyFieldName, typeof(KeyType) ).GetValue(ele, null) as KeyType;
                ret[key] = ele;    
            }

            return ret;
        }

    }
 
With the above you can access a list of any objects like this:
 
            var attendeesById = Util.ToDictionary<string, Attendee>(attendees, "Id");
 
Here for example it assumes Attendee class contains an "Id" string property.  You can use Util.ToDictionary with IEnumerable of any other types of objects. 

Another good use of generics to reduce code I had was when parsing XML; when parsing XML frequently you want to parse an element's value to a .Net type, using int.parse() for example, if the element is supposed to contain an int.  Well I would just use int.parse() to do the job except that sometimes the element can be optional, so it's again nice to have a generic helper method that takes in an XML element and returns the appropriate type after parsing, or null if it's optional.  For example, when using the following code to parse XML,
 
    WebRequest httpRequest = HttpWebRequest.Create(url);
    using (var httpResponseStream = httpRequest.GetResponse().GetResponseStream())
    {
        var xmlRoot = XElement.Load(XmlReader.Create(httpResponseStream));
        var xmlNamespace = XNamespace.None;
        ret = new List<Event>(from result in xmlRoot.Descendants(xmlNamespace + "item")
                                    select new Event()
                                    {
                                        Id = XmlUtil.GetXElementValue(result.Element("id"), true),
                                        Name = XmlUtil.GetXElementValue(result.Element(xmlNamespace + "name"), true),
                                        Description = XmlUtil.GetXElementValue(result.Element(xmlNamespace + "description"), false),
                                        VenueName = XmlUtil.GetXElementValue(result.Element(xmlNamespace + "venue_name"), false),
                                        VenueLatitude = Util.ParseOptional<double>(XmlUtil.GetXElementValue(result.Element(xmlNamespace + "venue_lat"), false)),
                                        VenueLongitude = Util.ParseOptional<double>(XmlUtil.GetXElementValue(result.Element(xmlNamespace + "venue_lon"), false)),

                                        ... 
                                    });
 
The following helper methods in Util come in handly, esp Util.ParseOptional, which is shared code to parse any type that I want to parse out of XML and that has a parse method.  The more shared code (i.e., the less code a developer writes) the less chance for bugs :).
 
    public class XmlUtil
    {
        ...
        public static string GetXElementValue(XElement xe, bool required)
        {
            string ret = null;

            if (xe != null)
            {

                if (xe.Value != null)
                {
                    ret = xe.Value.Trim();
                }
                else if (required)
                {
                    throw new Exception(xe.Name + " should contain a value");
                }
            }
            else if (required)
            {
                throw new Exception("required XML element is missing");
            }

            return ret;
        }

    } // XmlUtil class


    public class Util
    {
        /// <summary>
        /// generic method that parses the given string to the given type, taking into account
        /// that the given string might be null.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="arg"></param>
        /// <returns></returns>
        public static Nullable<T> ParseOptional<T>(string arg) where T : struct
        {
            Nullable<T> ret = null;

            if (!string.IsNullOrEmpty(arg) )
            {
                var methodInfo = typeof(T).GetMethod("Parse",
                                                     new Type[] { typeof(string) });
                ret = methodInfo.Invoke(null, new object[] { arg }) as Nullable<T>;

            }

            return ret;
        }
 
 In summary, I find .Net's generics and reflection features very helpful in abstracting functionality further and reducing even more lines of code written.  The other features demonstrated in the snippets like Linq to Xml, variable type inference (the "var" keyword), and object initializer (the ability to initialize properties in constructor) also help a lot in writing less code, and really speed up development.  I recently had to switch coding between C# and Java, and what a difference it was without them: the Java code is a lot more lines (and thus more chance for bugs) and you just end up typing more.  
 
Java was a revolutionary language (and I learned it before C#) but it seems like in the evolution of languages C# has surpassed it (at least in terms of the core language features).  

Developing Facebook app on your local machine

by James Shaw 12. January 2009 18:23

Facebook used to let a developer specify the Callback URL to be localhost (e.g., http://localhost:56570/MyApp/) for an IFrame-canvas app such that as you develop your app, you can just test it on your local machine before pushing it out to your hoster.  That's no longer the case: you cannot specify a callback URL containing "localhost" anymore.  So how do you keep using localhost for debugging purposes during development?

Here's what I did:

1. You need to have another Facebook app, say "My App Dev", registered for development purpose (you'll need one anyways once you've released your app and need to develop incremental features). 

 

2. In this app have settings that parallel the production Facebook app except the "Post-Authorize Redirect URL".  Have the "Post-Authorize Redirect URL" point to a small HTML file (say "TestPage.htm") on your hoster that redirects back to localhost :).  Create TestPage.htm like the following:

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Untitled Page</title>

</head>

<body>

<script type="text/javascript">

var newWindowLocation = "http://localhost:56570/ThePageIWantPostAuthorize.aspx" +

                                    window.location.search; // get the query string that FB tags on, including "?"

window.location = newWindowLocation; // redirect to localhost

</script>

</body>

</html> 

So your "Post-Authorize Redirect URL" would be http://apps.facebook.com/myapp_dev/TestPage.htm", for example.

 

3. Make sure in your code use the right Facebook APIKey and Secret for the right release, since now that we have 2 "Facebook apps" for the same app.  With .NET for example, you can have the following in web.config:

 

<add key="APIKey" value ="My App's key"/>

<add key="Secret" value ="My App's secret"/>

<
add key="APIKey.debug" value ="My App Dev's key"/>

<add key="Secret.debug" value ="My App Dev's secret"/>

and use compile-time macro to determine which set of values to use in your code:

#if(DEBUG)

var apiKey = WebConfigurationManager.AppSettings["APIKey.debug"];

var secret = WebConfigurationManager.AppSettings["Secret.debug"];

#else

var apiKey = WebConfigurationManager.AppSettings["APIKey"];

var secret = WebConfigurationManager.AppSettings["Secret"];

#endif

The same compile-time macro can help you pick the right set of other properties like db connection strings, since you'll probably want to use a local database instead of the one on your hoster during development. 

With the above trick, with just one additional HTML file (TestPage.htm) uploaded to your hoster and an additional Facebook app registration, you are back to your happy self debugging on your local machine :).

P.S. Obviously this trick won't work for FBML-based canvas.

Tags:

Tech

Customizing Sharepoint 2007: Quick Post

by James Shaw 17. July 2008 09:29

In my 1st technical post I'd like to share how I implemented a delicious-like feature in a Sharepoint site (called "Quick Post") that lets users post a file or Url to a Sharepoint document library with one click of a button on any page of the site; the user can tag each post just like with delicious, and the site has tag cloud and searching-by-tags features for users to find info later; the user will get suggestions on existing tags when he tags. 

The following is a screen shot of the pop-up window after the user clicks on the Quick Post button in the site:  



The type just gives the user a choice on one of the two different document libraries.  The location is a toggle between the ASP.NET FileUpload control for file and TextBox control for Url, using Javascript.  

With this custom UI, the user is able to post a file or Url to the document library without leaving the page he's on, and in one screen (unlike the built-in multiple Sharepoint pages for adding a file or Url, and any tags in the tag field of the document library); the custom UI also has tag suggestion to maximize tag reuse :).

The Pop-up Window

The pop-up window you see in the screen shot is a DHTML window (which is better than a browser window because of pop-up blocker).  I'm all for re-using others' code as much as possible :), so for the pop-up window I used and would recommend this 3rd party widget.  Just include the 3rd party widget in your page that pops up the window (in our case the master page since the QuickPost button needs to show up everywhere):

    <link rel="stylesheet" href="http://devcentral/_vti_bin/windowfiles/dhtmlwindow.css" type="text/css" />   
    <script type="text/javascript" src="http://devcentral/_vti_bin/windowfiles/dhtmlwindow.js">
   
    </script>

and have the button click call the following javascript function:

        var quickPostWin = null;
       
        function openQuickPostWindow ()
        {
            quickPostWin = dhtmlwindow.open("quickPostBox",
                                            "iframe",
                                            "http://devcentral/Pages/QuickPost.aspx",
                                            "Quick Post",
                                            "width=650px,height=400px,resize=1,scrolling=1,center=1",
                                            "recal");                       

            if (quickPostWin.onclose == null)
            {
                quickPostWin.onclose=function(){ //Run custom code when window is being closed (return false to cancel action):
                    return true;
                }
            }               
           
        }

The content inside the window comes from a ASP.NET user control (.ascx).  The easiest way I found to develop custom pages in Sharepoint is to develop it in Visual Studio (so you take advantage of the designer support, code-behind, etc), and host it in a Sharepoint web part: a page viewer web part to host .aspx, or like in this case, a 3rd party smart part web part to host .ascx; the page shown in the DHTML window (http://devcentral/Pages/QuickPost.aspx) is just a Sharepoint page with one smart part web part that hosts a .ascx.  Sharepoint still has some way to go in terms of its integration with Visual Studio for development, and this is kind of a stopgap until MSFT builds the functionality of developing custom pages for Sharepoint right into Visual Studio.

AJAX functionality

As seen in the screen shot, Quick Post provides suggestions as the user starts typing in tags, just like delicious.  To add AJAX functionality like this, I used the AJAX Control Toolkit from codeplex; it contains quite a few extenders to ASP.NET controls to add AJAX to your web page, and in this case I used the auto-complete extender to the tags text box.

To use AJAX Control Toolkit in Sharepoint, make sure to reference it and include the ScriptManager in the page.  Since in our case we want AJAX functionality in every page (as in most cases I think), I just put the following in the master page:

<%@ Register Tagprefix="ajaxToolkit"
             Namespace="AjaxControlToolkit"
             Assembly="AjaxControlToolkit, Version=3.0.11119.26320,
             Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" %>

...

    <ajaxToolkit:ToolkitScriptManager ID="ScriptManager1" runat="server" />

Here I'm using AJAX Control Toolkit's implementation of the ScriptManager rather than the one in ASP.NET AJAX, as the former is supposed to be more efficient at sending the javascript down to the client.

The auto-complete extender itself is pretty straight-forward as documented: in my implementation, the web-service responding to the user key strokes in the tags text box just iterates over all records in the document library, extracts and parses the tags from the comma-separated tag field (using Sharepoint API's), and return the tags matching the given prefix.

There's perhaps one tricky thing in the web service.  In our Sharepoint site, the user is free to create sub-sites, and those sub-sites can contain library items with tags as well, so to make sure we capture all existing tags from all sites in this site collection, you need to make sure either every user has the permission to iterate over all sites, or elevate the privilege in the web service code somehow, .e.g, by impersonating a user that for sure has permission to do it:

            SPUser wssAccount = null;
            using (SPSite siteCollection = new SPSite("http://devcentral"))
            {
                // need to impersonate to iterate over all sub-sites
                wssAccount = siteCollection.OpenWeb().AllUsers["corp\\dcentral_wss_svc"];
            }
           
            using (SPSite impersonatedSiteCollection = new SPSite("http://devcentral", wssAccount.UserToken))
            {
               // iterate over all sites, get all the tags, and return matching tags

               ...

            }

The above will also do the trick any time your custom code using the Sharepoint API's needs to run in elevated privilege mode.

Storing Url in a Document Library

Sharepoint document libraries can have multiple content types, and when it does, the user can choose which content type it is when adding a record to a document library.  There's a "Link to a Document" content type that's well suited for storing Url's in a document library; in fact it can store link to another document as well.  For our Sharepoint site, however, we just need to support storing Url's.  

It's however not well documented how to add a document link programmatically, as I had to do for our custom UI.  When a user adds a document link to a document library, what happens behind the scenes is that Sharepoint actually creates an aspx file on the fly that does nothing more than redirecting to the document link Url you give, and it's the aspx file that's stored in the document library, just like a document stored in the document library.  A rather convoluted way to store links in my opinion, but that's the way the document link content type works.

After bugging him about it, Cliff Green from MSFT wrote up how to programmatically add a document link to a document library in his blog :).  Essentially you have a aspx template file that you read into memory each time you add a document link to a document library, after filling in the Url in the template file in-memory.

Conclusion

In this blog post, I've hopefully covered how to have custom pages in Sharepoint (leveraging Visual Studio designer), adding AJAX to your page, and how to have Url's in a document library.  In future posts I will share more customizations I've done in Sharepoint.  Hope this has been a useful read :).

TechEd 2008

by James Shaw 6. June 2008 13:51

TechEd 2008 just finished.  It was a blast!  I enjoyed it a lot.  Microsoft did a great job throwing a fairly smooth event, given the number of attendees, and pampering of the attendees, including a night at the Universal Studio.  See my picture with the Simpsons there:

Look.  They even provided bean bags for us to sleep on when we are exhausted from the conference:

The sessions themselves were about 1/3 bad, 1/3 good, and 1/3 excellent.  I have to give MS credit for trying to get feedback; I heard from one of the speakers that they have a monitor that constantly shows the bottom rated speakers who probably won't be invited back next year.  I also noticed a pattern of the MS speakers there: they are all PM's rather than developers.  Hmm...  Even most of the staff manning the product booths were PM's.  I asked one of them why there's no developers; he said that they'd rather work and finish the product.  Do you believe that?  :).

The excellent sessions I attended were (in case you want to view their videos online): "Asynchronous ASP.NET", "Silverlight Tips", and "ASP.NET AJAX Extensions Deep Dive" by Jeff Prosise, "ASP.NET Performance Black Belt" by Steven Smith, and "SEO for Developers" by Nathan Buggia.  I only went to the sessions based on the topic, and Jeff's sessions always overflowed.  Apparantly a lot of people went to sessions based on the speaker :).

These sessions are also good to check out: "IIS 7 Security and Tuning" by Ruslan Yakushev, "Silverlight Controls" by Karen Corby, "REST web services using WCF" by Jon Flanders, and "Optimizing Javascript and IE" by Cyra Richardson. 

At the conference, I was really impressed by Silverlight's capabilities and felt that it's really going to disrupt the web space.  I attended a BofF session (i.e., a peer discussion among TechEd attendees w/o MS) on Silverlight vs AJAX, and as people started listing out the pro's and con's of AJAX and Silverlight, it became clear that RIA like Silverlight is the superior solution and the next step of evolution in web development; AJAX is really an intermediate stage towards richer content/easier development (Google is solving ease of development with GWT, but there are just things you can't do with AJAX that you can do with Silverlight/plugin-type of model).  The one major drawback is searchability (Plugin model is still a black box that search engines can't crawl), but like Nathan said, it's something that all search engines are trying to crack and find a solution of. 

With all the talk about Silverlight, I also felt a bit deja vu.  In my college years Java applet was all the hype of the day, and, except more advanced graphics and a declarative model, it pretty much has everything Silverlight has (including a bridge with Javascript/Browser DOM).  Java applets didn't take off for some reason (non-ubiquitous broadband?).  It seems like we're going back full circle to a plugin-model of web development like Flex/Flash and Silverlight. 

I also saw an interesting book at the conference store and couldn't resist buying it:

I've skimed through it and think it's a pretty candid assessment of Microsoft today; it's written by a well-know Microsoft analyst Mary Jo Foley.  Check it out!

Overall it was a great experience for my 1st time at a Microsoft conference.  The MS people were friendly technical enthusiasts who seem genuinely passionate about technology, and I felt right at home talking to them :). 

The Perfect Laptop

by James Shaw 18. February 2008 12:39

I really gotta get in the habit of writing blogs...

Recently I just bought a new personal laptop, to replace my venerable IBM Thinkpad T41.  Thought it'd be good to share my experience:

Originally I was just going to buy the latest Thinkpad (T60 at the time); seemed like a logical choice.  I was at the same time upgrading my work laptop to a T60p; good thing I waited to see how it is before buying one for my personal use.  The laptop was heavier than T41, freezes half of the time whenever I undock and bring it back from sleep, or whenever I dock.  It was terrible.  A lot of my co-workers also had the same problem, and the IBM service rep on-site did his best (including putting a jumper on one of the docking station pins and some software option) but the freeze still happened.  Nowadays I just use it like a desktop, i.e., always having it docked and remoting to it from another laptop when I need to access it, which totally defeats the purpose of having a laptop.  It seems that since the acquisition by Lenovo the quality of thinkpad just went way down...

I then started looking for another brand for my personal laptop.  In the end I settled for a macbook (BTW I bought it from ecomelectronics; probably the cheapest place for it).  Still getting used to it; there has been lots of little things that annoyed me, like the extra small screws that need a special screw driver to open to install more memory, and the stupid bootcamp download from apple website that expires as soon as you run it (since it's part of Leopard and they don't want people using the beta one; why don't they just disable the link on their website if it's not meant to be used?).  What frustrated me the most was that the drivers bootcamp installs for Vista by default conflict and whenever you play music (or game) it skips.  It cost me quite a few hours before finally finding a solution (see Smaels' 1st post on the page).

Overall I'm just disappointed at the quality of laptops (and electronics in general) nowadays.  Seems like companies just try to rush to market without doing enough QA; these 2 scenarios are so basic (docking and undocking a laptop; playing music after installing Vista on a macbook) that I'm apalled to see that Lenovo and Apple released their products without covering them.

For now I'm fine with the macbook.  It's still not as good as my old T41 (where everything just works as expected), but probably the best laptop experience I'll get.  One good thing about owning a macbook for a software developer is that you can test your web app on all 3 major browsers (IE, Firefox and Safari).

Powered by BlogEngine.NET 1.4.5.0
Theme by Extensive SEO