I Love ReSharper

Wed 24 January, 2007

This was going to be a post about the different VS 2005 plugins that I use, and some other useful programs for my everyday (most of them Open Source, by the way); but while I was writing it turned itself little by little into a love letter to Resharper. I can only say good things about this productivity plugin.

This are only some of its features:

  • Automatic warning and error checking while you write code; similar to the automatic ortographic revision from MS Office but quite more smart.
  • Better Intellisense. By far, my preferred feature and the one you tend to note first. If you want it to, ReSharper can completely replace VS2005’s Intellisense with its own, faster and more complete. You have the possibility of setting the same font for Intellisense and source code, you can replace Intellisense icons with ReSharper’s set, and add a pop-up with the complete method signature.
  • Code auto-completing, you write the first three characters of virtually anything and ReSharper is already displaying a list of alternatives. If what you’re writing is a method call, pressing TAB inserts the complete method name along with the () and the ;.
  • Templates.
  • Refactoring: you can extract properties from variables, or methods from code: you select a code block, select to extract it as a method, give to it a name and some options, among them are a list of possible parameters that ReSharper automatically detects and voilá!: method created.
  • Advanced search for method use.
  • Type hierarchy view
  • Unit test running with NUnit or csUnit from the IDE.
  • Context actions. In some sections of your code you can see a small floating window with a lightbulb icon. ReSharper wants to help: opening that window with your mouse (or even better, pressing ALT+Enter) ReSharper will show you a list of all the possible actions it can make depending on the code context: check for null values, reorder if sentences, refactor string concatenatios with an StringBuilder, delete unused references, and so on.
  • Automatic detection of unused variables, methods or references. If you declare a variable at the beginning of your code that you later don’t use ReSharper will remark it using a grey color (trust it, it’s quite more noticeable than it sounds), and with that you know that it’s not being used anywhere. ReSharper uses the same color to remark redundant and useless casts or uses.
  • Quick jump to a method or variable definition: using CTRL+Click on the method or variable name you can jump directly into the method or variable definition, if the code is avaliable.
And these are only the tool’s highlights, the tasks I do daily with it. Definitely, it’s the best buy I’ve ever made for a Visual Studio add-in; mostly because I used this last Christmas offer and got it for $99 instead of its normal price tag of $249, which is quite unexpensive if your firm buys it for you: not my case. I bought ReSharper for my exclusive use.

JetBrains development team make Visual Studio 2005 a better yet IDE with this product. The ongoing rumor that they are developing their own version of a .NET IDE, building of IDEA’s true and tested success is, sadly, outdated (look the comments), because it seems the project is abandoned. A real pity.

VirtualBox

Tue 16 January, 2007

When I was beginning in software development, I worked for a firm which (among other things) sells a software application for creating plastic cards. That software was off-the-shelf, shrinkwrapped.

As you can see, the software had to be able to install itself easily and couldn’t use additional components. To make sure of it, every time we compiled a new distributable version we made compatibility tests: on my desk I had four machines, one for development and three older PCs for tests. One of the machines had a Windows 95, another a Windows 98 and the last one a Windows NT; and each of them had only that and the necessary drivers installed. Our software had to work on naked machines, and with each major upgrade we had to format the test machines and reinstall their operating systems, to make sure a perfect installation of our application. The process was so repetitive that I ended unwillingly memorising the serial numbers of each OS.

We managed to speed up the process quite a bit thanks to Norton Ghost: you could make an "naked" OS image file for each machine and after the installation tests of our application, we could reinstall the software from the Ghost image, which quite faster and automatic.

But it was still a pain.

I would have been really happy those days for virtual machine software: either it didn’t exist by that time or I didn’t know about it. For those of you who don’t know what I’m talking about, Para los que no sepáis de lo que hablo, las virtual machines are programs that create a virtual environment between your physical hardware and the operative system installed in that hardware; and in that virtual space you can install another operative system completely different to the one physically installed, in a completely sandboxed environment. You can have a Windows 2003 Server inside a Windows XP, for example.

It’s easier to do than it looks: you assign inside the hard drive of your machine a file that’s going to be the virtual machine hard drive; and you set an amount of RAM to the virtual machine (RAM that’s going to be substracted from your total, physical RAM avaliable, so you need to have plenty for this). You can also mount virtual CD or floppy drives, allow the virtual machine network access, etc. To install an OS you just have to mount an ISO file containing an auto-bootable OS and run the virtual machine. From that point, you simply follow the OS installation instructions as if, in fact, we were installing a new OS in a machine built inside our machine.

I’ve personally used two virtualisation environments: Microsoft Virtual PC, which its version 2004 is free (as in beer) (2007 version is not) and I’ve been quite satisfiied with it: it’s a great virtualisation software, intuitive and easy of use, and the one I use in my workplace.

And the one I’ve just tested at home is InnoTek VirtualBox, Open Source and free (as in freedom). For me at least, the later is more important: I frankly don’t see myself capable of understanding, much less modifying, the source code of one of these things.

Right now I’m writing this entry from a virtual machine made with VirtualBox and running an Ubuntu Dapper Drake. No, I’m not going to the light side: it simply was the ISO file most at hand at the moment to test VirtualBox. In fact, my target for the next days is installing Vista. Long life the dark side! =)

So if you didn’t know virtualisation software, my advice to you is to give it a try: it’s simple to setup, intuitive to use and thanks to this technique we’ll have a safe environment to tinker with harmlessly.

Random Surname Generator

Fri 12 January, 2007

Aswering the challenge raised by Jon Galloway on his must-read blog, here’s is my solution to generate some random and fake surnames. Code has plenty of comments. Feel free, of course, to comment questions, criticism or even better, a cool refactor! =)

class Program
{
    
//An arraylist to contain ALL possible digrams (see below)
    
private static ArrayList AllDigrams = new ArrayList();
    
//An array list to contain ALL possible letters (see below)
    
private static ArrayList AllLetters = new ArrayList();
    
//A random seed for the main random generator
    
private static Random rSeed = new Random()
    
//A multidimensional array specifying the random "bones" of a surname. 
    // "A" is a vowel letter, "B" is a consonant letter
    //"AB is a vowel + consonant digram, and so on…
    //These combinations are made by hand, you could add or delete 
    //to fine-tune the kind of surnames you get
    
private static string[,] SurnameRandomTypes = new string[106]
            {
                {
"A",  "BA""B",  "A",  "BA"""},
                {
"B",  "A",  "BA""",   "",   ""},
                {
"A",  "BA""A",  "B",  "A",  "BB"},
                {
"A",  "B",  "B",  "A",  "BA"""},
                {
"A",  "BB""AB""A",  "",   ""},
                {
"BA""B""B",  "A",  "B",  "A"},
                {
"B",  "AB""A",  "BB""A",  "AB"},
                {
"B",  "AB""B",  "A",  "BB""AB"},
                {
"A",  "BB""A",  "B",  "AB"""},
                {
"AB""AA""B",  "A",  "B",  "A"},
            }
;

    /// <summary>
    /// 
    /// </summary>
    
static void Main()
    {
        
bool exit = false;
        
//We load ALL the possible digrams in memory, 
        //in the AllDigrams arraylist
        
LoadDigrams();
        
//Same for the possible letters, 
        //in the AllLetters arraylist
        
LoadLetters();
        do
        
{
            
for (int 0i <20i++)
            {
                Console.WriteLine(
"Random Surname:    {0}"
                    GenerateRandomSurname())
;
            
}
            Console.WriteLine(
"Press ENTER to generate another batch, X to quit");
            string 
sInput Console.ReadLine();
            if 
(sInput.ToString() == "x" || sInput.ToString() == "X")
                exit 
= true;
        
while (exit == false);
        
Console.ReadLine();
    
}

    /// <summary>
    /// This method generates a random surname and returns it.
    /// </summary>
    /// <returns></returns>
    
static string GenerateRandomSurname()
    {
        
string sRet "";
        
Random r;
        
//New random seed for the randomizer
        
= new Random(rSeed.Next(11000));
        
//and we get a random index for the 
        //SurnameRandomTypes multidimensional array
        
int rndIndex r.Next(09);
        
//Then we read the different "columns" of 
        //that "row" and decide if we need a vowel, 
        //a consonant, a vowel-consonant digram, 
        //or whatever
        
for (int 0i < 6i++)
        {
            
switch(SurnameRandomTypes[rndIndex, i])
            {
                
case "AA":
                    sRet +
getDigram(DigramType.TwoVowels);
                    break;
                case 
"AB":
                    sRet +
getDigram(DigramType.VowelAndConsonant);
                    break;
                case 
"BA":
                    sRet +
getDigram(DigramType.ConsonantAndVowel);
                    break;
                case 
"BB":
                    sRet +
getDigram(DigramType.TwoConsonants);
                    break;
                case 
"A":
                    sRet +
getLetter(LetterType.Vowel);
                    break;
                case 
"B":
                    sRet +
getLetter(LetterType.Consonant);
                    break;
                default
:
                    
break;
            
}
        }
        
//And we return the generated surname 
        //making the first letter upper case
        
return sRet.Substring(0,1).ToUpper() + 
            sRet.Substring(
1);
    
}

    #region Letters and Digrams
    
    
#region Structs and Enums
    
/// <summary>
    /// With this enum we’ll know what 
    /// the digram is made of
    /// </summary>
    
private enum DigramType
    {
        TwoVowels,
        VowelAndConsonant,
        ConsonantAndVowel,
        TwoConsonants
    }

    /// <summary>
    /// With this enum we’ll know is a letter
    /// is a vowel or not
    /// </summary>
    
private enum LetterType
    {
        Vowel,
        Consonant
    }

    /// <summary>
    /// A digram. With the actual digram ("th", for example)
    /// we store the range of frequency it appears on the 
    /// english language and its type
    /// </summary>
    
private struct Digram 
    {
        
public int iniRange;        //The beginning of its range
        
public int endRange;        //The end of its range
        
public string Value;        //Its actual content
        
public DigramType Type;     //Its type
    
}

    /// <summary>
    /// A letter. With the actual letter ("a", for example)
    /// we store the range of frequency it appears on the 
    /// english language and its type
    /// </summary>
    
private struct Letter
    {
        
public int iniRange;        //The beginning of its range
        
public int endRange;        //The end of its range
        
public string Value;        //Its actual content
        
public LetterType Type;     //Its type
    
}
    
#endregion

    /// <summary>
    /// This method returns the value 
    /// of a random digram of the specified type
    /// </summary>
    /// <param name="type">The type of digram the caller needs</param>
    /// <returns>The digram VALUE ("th", for example)</returns>
    
static string getDigram(DigramType type)
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Digram nuDigram = new Digram();
        do
        
{
            
//We get a random number inside the TOTAL 
            //range of ALL the digrams
            
int freq r.Next(05548)//See LoadDigrams()
            
foreach (Digram digram in AllDigrams)
            {
                
//And if the random number is inside the 
                //current digram frequency range, 
                //we’ve got a winner!
                
if (freq >digram.iniRange && 
                    freq <
digram.endRange) 
                        nuDigram 
digram;
            
}
            
//But only if it’s of the type we need
        
while (nuDigram.Type !type);
        return 
nuDigram.Value;
    
}

    /// <summary>
    /// Gets a single letter VALUE of the specified type
    /// </summary>
    /// <param name="type">the type of Letter we need</param>
    /// <returns>the Letter VALUE</returns>
    
static string getLetter(LetterType type) 
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Letter nuLetter = new Letter();
        do
        
{
            
//We get a random number inside the TOTAL 
            //range of ALL the letters
            
int freq r.Next(010025)//See LoadLetters()
            
foreach (Letter letter in AllLetters)
            {
                
//And if the random number 
                //is inside the current digram 
                //frequency range, we’ve got a winner!
                
if (freq >letter.iniRange && 
                    freq <
letter.endRange)
                        nuLetter 
letter;
            
}
            
//But only if it’s of the type we need  
        
while (nuLetter.Type !type);
        return 
nuLetter.Value;
    
}

    /// <summary>
    /// Simple boolean check to see if a given single letter is
    /// a vowel or a consonant
    /// </summary>
    /// <param name="checkLetter">The letter to check</param>
    /// <returns>True if vowel, false if else</returns>
    
static bool IsAVowel(string checkLetter)
    {
        
switch(checkLetter.ToLower())
        {
            
case "a":
            
case "e":
            
case "i":
            
case "o":
            
case "u":
                
return true;
            default
:
                
return false;
        
}
    }

    /// <summary>
    /// Thanks to the University of Bristol Department of Computer Science
    /// (and Google) I’ve managed to get a list of all the letters in the 
    /// english alphabet and their frequency of use in the English language
    /// Since Random deals with integers, I’ve multiplied all these values * 100
    /// The sum of all of them should be 10,000, which when calculating the 
    /// range of random numbers to draw should be 10,000 + the number of possible 
    /// letters; because the first letter range is from 0 to 1231, and the next 
    /// letter range begins en 1232 (prior end range +1)
    /// In the case of single letters this is correct, and the ranges are from 
    /// 0 to 10,025. 
    /// 
    /// But, I don’t know WHY, in the case of digrams the range goes 
    /// from 0 to 5,548. The frequency percentage of all the avaliable digrams,
    /// each of them multiplied by 100 and all of them added is NOT 10,000 as it 
    /// should be. Anyone knows why?
    /// </summary>
    
static void LoadLetters()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        // Percentage Frequency of Single Letters
        //
        //  E 12.31      L 4.03     B 1.62
        //  T  9.59      D 3.65     G 1.61
        //  A  8.05      C 3.20     V 0.93
        //  O  7.94      U 3.10     K 0.52
        //  N  7.19      P 2.29     Q 0.20
        //  I  7.18      F 2.28     X 0.20
        //  S  6.59      M 2.25     J 0.10
        //  R  6.03      W 2.03     Z 0.09
        //  H  5.14      Y 1.88      

        string[] _letterValue 
        
{
            
"e""l""b""t""d""g""a""c""v",
            
"o""u""k""n""p""q""i""f""x",
            
"s""m""j""r""w""z""h""y"
        
};
        int
[] _letterFreq 
        
{
            
123140316295936516180532093
            
794310527192292071822820,
            
659225106032039514188
        
};
        int 
lastEndRange 0;
        
//Examining those hard-coded arrays we fill 
        //the AllLetters collection of letter objects, 
        //assigning each one its range, value and LetterType
        
for (int 0i < _letterFreq.Lengthi++)
        {
            Letter nuLetter 
= new Letter();
            
nuLetter.iniRange lastEndRange;
            
nuLetter.endRange lastEndRange + _letterFreq[i];
            
lastEndRange nuLetter.endRange + 1;
            
nuLetter.Value _letterValue[i];
            
nuLetter.Type IsAVowel(_letterValue[i]) ? 
                LetterType.Vowel : LetterType.Consonant
;
            
AllLetters.Add(nuLetter);
        
}
    }
     
    
/// <summary>
    /// See LoadLetters comment
    /// </summary>
    
static void LoadDigrams()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        //
        //  TH  3.15   TO  1.11   SA  0.75   MA  0.56 
        //  HE  2.51   NT  1.10   HI  0.72   TA  0.56
        //  AN  1.72   ED  1.07   LE  0.72   CE  0.55
        //  IN  1.69   IS  1.06   SO  0.71   IC  0.55
        //  ER  1.54   AR  1.01   AS  0.67   LL  0.55
        //  RE  1.48   OU  0.96   NO  0.65   NA  0.54
        //  ES  1.45   TE  0.94   NE  0.64   RO  0.54
        //  ON  1.45   OF  0.94   EC  0.64   OT  0.53
        //  EA  1.31   IT  0.88   IO  0.63   TT  0.53
        //  TI  1.28   HA  0.84   RT  0.63   VE  0.53
        //  AT  1.24   SE  0.84   CO  0.59   NS  0.51
        //  ST  1.21   ET  0.80   BE  0.58   UR  0.49
        //  EN  1.20   AL  0.77   DI  0.57   ME  0.48
        //  ND  1.18   RI  0.77   LI  0.57   WH  0.48
        //  OR  1.13   NG  0.75   RA  0.57   LY  0.47 

        string[] _digramValue 
        
{   
            
"th""to""sa""ma""he""nt""hi""ta",
            
"an""ed""le""ce""in""is""so""ic",
            
"er""ar""as""ll""re""ou""no""na",
            
"es""te""ne""ro""on""of""ec""ot",
            
"ea""it""io""tt""ti""ha""rt""ve",
            
"at""se""co""ns""st""et""be""ur",
            
"en""al""di""me""nd""ri""li""wh",
            
"or""ng""ra""ly"
        
};
        int
[] _digramFreq 
        
{
            
31511175562511107256,
            
17210772551691067155
            
1541016755148966554
            
145946454145946453
            
131886353128846353
            
124845951121805849
            
120775748118775748
            
113755747
        
};
        int 
lastEndRange 0;
        
//Examining those hard-coded arrays 
        //we fill the AllDigrams collection
        //of Digram objects, assigning each 
        //one its range, value and DigramType
        
for (int 0i < _digramFreq.Lengthi++)
        {
            Digram nuDigram 
= new Digram();
            
nuDigram.iniRange lastEndRange;
            
nuDigram.endRange lastEndRange + 
                _digramFreq[i]
;
            
lastEndRange nuDigram.endRange + 1;
            
nuDigram.Value _digramValue[i];
            if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.TwoVowels;
            else if 
(!IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.ConsonantAndVowel;
            else if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                !IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.VowelAndConsonant;
            else 
nuDigram.Type DigramType.TwoConsonants;
            
AllDigrams.Add(nuDigram);
        
}
    }
#endregion
}


Colorized by: CarlosAg.CodeColorizer

And here’s an example of the surnames it makes:

  • Andaso
  • Estecon
  • Ovloso
  • Eleasawh
  • Athene
  • Tertore
  • Setolyiis
  • Face
  • Lirsuco
  • Rofsender

kick it on DotNetKicks.com

XNA development

Fri 1 September, 2006

Although everyone and his brother has already posted this, I gotta try…

Microsoft has made avaliable the beta of XNA Game Studio Express, a set of free tools for programming games targeted to Windows and XBox. It includes the XNA Framework, which is no less than a collection of managed libraries geared towards a (much needed) simplification of using DirectX.

Oriented to students and amateur programmers (and actually reaching out to almost every programmer1) at the moment XNA Game Studio is only compatible with Visual C# 2005 Express.

Tehre’s already a site (that I know of) collecting all the download links for all the needed tools (and yeah, they’re all free); and some tutorials to boot: XNA Development.

I don’t know why, but I don’t think I’m going to get out much this weekend…


1.- C’mon, which one of you has not once dreamed about game programming? It’s the Top Gun of computer programming!

SubSonic

Wed 30 August, 2006

Wow. I’ve just hurt myself: I’ve just seen Rob Conery’s webcast about the little library he’s created for .NET 2.0 and myu jaw has opened so much all by itself, and not precisely yawning, that it has crashed against my desk.

As Conery himself says, he’s followed Ruby on Rails for creating SubSonic (previously known as Action Pack). SubSonic is a Zero Code DAL, or Data Access Layer. Just like in Ruby, complete with scaffolding.

Beware, SubSonic is not a .NET implementation of Ruby On Rails. That’s MonoRail. It’s not even an adaptation, because RoR is a complete framework with Modell-View-Controller , data access layer and all the works. SubSonic is just an amazing data access layer.

I haven’t got the time (yet) to play a little with it, but if you’d like to stop feeling envy while watching Rails webcasts or presentations, take a look at the Introduction to SubSonic webcast.

Beware you jaws.

Update.- I forgot to link this excellent introduction by Jon Galloway. I’m of the same opinion: Microsoft should hire Conery and include the ActionPack.dll as part of Atlas. Or for giving SubSonic MVC capabilities. Or doing something with this stuff.

Google Code Hosting

Mon 31 July, 2006

Or how Google made its very own SourceForge.

Google has made avaliable for all of us with a GMail account, Google Code Hosting, a repository for Open Source projects hosted by Google. It uses Subversion, and allows the project administrators to manage a bug tracker, the project collaborators, add links to external webpages about the project, and so on.

I’ll keep telling you things as I keep trying it.

SharpDevelop 2

Two very good news for this post. First of all is the recent update of our favorite Open Source IDE for C#, SharpDevelop, to Framework 2.0. Yeah I know, this isn’t strictly new, because it’s not recent (and how difficult is being in this day and age!): it was the last July the 17th when it made its official appereance the definitive version of SharpDevelop 2 2.0.

But now let’s talk about the second piece of news. After downloading it ( 4.2 Mbs) and install it (less than five minutes) I begin to try it. When creating a new try it out solution (another Hello, world!) I began to tremble: compatibility troubles on sight.

SharpDevelop 1 used combines, file swith a .cmbx extension, as its central type file: SharpDevelop combines were the equivalent to VS .NET solutions. So when you spotted on your hard drive a .cmbx file you knew it was SharpDevelop’s, and it was by default associated on Windows Explorer to that application. Files such as .cs or .resx you could decide wich IDE you want them associated to.

But now the combines are lost, being replaced with solutions, just as in Visual Studio. And their file extension is the same: .sln.

The pain. I was already picturing myself creating separate root folders for Vs and SharpDevelop projects, I was already picturing both IDEs fighting for the .sln association like two furious web browsers, I was already picturing myself corrupting forever a solution after opening it with the wrong IDE, and so on.

Hold your horses: after creating a nice, little and silly solution in VS .NET 2005, I’ve been able to open, modify and compile it without fuss in SharpDevelop 2. And viceversa, the same solution can be opened, modified and compiled by both IDEs with no troubles that I detected.

I wish I could do a much more serious testing, for example an ASP .NET application, but for now I can declare both IDEs totally compatible. And this is a very good news.

In the least expected places…

Mon 5 June, 2006

… is where you find gold. Looking at the Google Analyzer stats for this blog, I’ve found the blog of an ex-Mac zealot and I’ve started reading the sad, sad story of his relationship with the apple firm. And I’ve found, where I least expected to, something I’ve been looking for a long, long time:

A real fucking keyboard!
Flickr’s service mandates me to link the image to Flickr. Bleuargh!

Yeah, it’s a keyboard. But, is that any keyboard? Nooo… is one of those older keyboards, made with metal instead of that disgusting plastic membrane, one of those keyboards that when one was furiously typing it looked like the whole roof was going to fall upon you with the klackety-klackety-klack; what became to be known as an IBM Keybooard. Yes, sir, a keyboard of old. A real fucking keyboard, not the little pieces of crap they make today.

Fast as ligthning I’ve readied my ninja-shuriken-credit-card and I’ve ordered a 105 keys model, with the keyboard layout in spanish. If you want more information, they’re avaliable at Pc Keyboard.

Planning the future

Mon 8 May, 2006

On this excellent post, Mark Pilgrim tells us about his current data storage needs, and what he foresees about it. And the solution is, at least, difficult and/or expensive.

Although I don’t have (yet) his storage problems, I was beginning to think about the same issue, so his post has come really handy. And in the comments I’ve seen a recomendation for Infrant’s ReadyNAS, which Amazon resells. At $2,199, the 2 Tb X-RAID NAS seems very likable to me. I think I know where my next bonus pay is going.

Besides, it’s freakin’ cool.

Web 2.0 and all that…

Mon 10 April, 2006

Via Mike Gunderloy (again… if you’re not suscribed already, don’t know what you’re waiting for) I’ve found two amazing libraries for ASP .NET.

First, BusyBoxDotNet, a server control which displays a wait message on the page stating that the server is performing a time-consuming operation and unabling the user to do nothing else but wait. The message is shown on a layer which moves automatically when scrolling, and allows to gray out and lock the back of the page (that is, all the rest of it) while the message is displayed and the task is (hopefully) being performed, to dissapear without a fuss at the end. You can try a demo right here. Really useful. Kudos.

And second, hot in the Web 2.01 hype, we find MagicAjax.NET. It allows us to enter AJAX techniques on standard out-of-.the.box ASP .NET server controls, which I find particularly useful for applying AJAX on an already developed or well underway ASP .NET application. It also has a demo avaliable.


1 And no, it’s not only to make rounded borders to visible layers. Neither putting an RSS feed on your pages, neither using words such as sinergy, community, collaborative or even tags. Even if you place a Beta label neatly by the side of your application’s title, you fall short. Besides all that, you have to make your web application more intuitive, more responsive, more agile. Less goings to the server for information, at least in appeareance. Yes, it has to be as similar as possible to an old desktop application, even imitating them completely. Live and see.