Getting Real

Ce n’est pas tous les jours que je lis un livre qui est aussi en phase avec ce que je pense…

Le sujet est le développement d’applications web mais cela peut très bien s’adapter à beaucoup d’autres domaines. J’aimerais voir des sociétés française qui partagent ces méthodes et cette façon de concevoir le développement logiciel! En tout cas, pour le moment, j’en ai trouvé aucune qui s’y rapproche, et c’est bien dommage!

En plus, c’est lisible gratuitement en ligne, alors je vous le conseille:

http://gettingreal.37signals.com/

Installing Redmine on Ubuntu 8.10

This time I use my blog as a notepad in case I need to reinstall redmine on Ubuntu again. Perhaps it’ll help also some people.

Go root
su

Install ruby gem
apt-get install rubygems

Since Ubuntu 8.10 uses 1.2 an redmine needs 1.3.1, upgrade gem:
gem install rubygems-update
/var/lib/gems/1.8/bin/update_rubygems

Install rails:
gem install rails -v=2.2.2

Install mysql:
apt-get install libmysqlclient15-dev ruby1.8-dev
gem install mysql

Install apache:
apt-get install apache2

Install passenger:
apt-get install build-essential libopenssl-ruby libapr1-dev apache2-prefork-dev
gem install passenger
passenger-install-apache2-module

Add the “LoadModule” line in /etc/apache2/mods-available/passenger.load
Add the 2 following line line in /etc/apache2/mods-available/passenger.conf

(optional)Install php5:
apt-get install php5

Restart apache:
/etc/init.d/apache2 restart

Ensuite suivez les instructions à partir de ‘installation’ sur le site redmine
Installation

Optimisation

++i ou i++ ? Tel est la question!

Combien de fois j’ai vu :

for(int i = 0; i < vec.size(); ++i)
{
...
}

Le plus étonnant, c’est la réponse donnée par un programmeur qui fait cela. La plupart du temps, il répond que c’est plus rapide, c’est une optimisation… Sauf que dans 99.99% des cas, si on demande combien on gagne concrètement, il ne sait pas répondre, tout simplement car c’est une optimisation pifométrique. Rien ne dit que ce code sera plus optimisé et encore moins que l’application ira plus vite.

Après, on trouve d’autres personnes qui vont plus loin :

int vsize = vec.size();
for(int i = 0; i < vsize; ++i)
{
...
}

J’ai même vu :

int vsize = vec.size();
for(; --vsize != 0;)
{
...
}

Le problème n’est pas l’optimisation en elle-même mais plutôt le fait d’optimiser au pif n’importe quelle partie du code quitte à la rendre moins lisible sans aucun gain réel de performance. Surtout que la règle des 80/20 est très souvent applicable. Seulement 20% du code prend 80% du temps CPU.

Si on veut vraiment optimiser du code, alors il faut le faire comme une tâche à part entière et suivre un processus simple:

  • Bencher son application
  • Détecter la fonction la plus lente
  • Optimiser la fonction la plus lente
  • Recommencer à l’étape 1

Au moins, on sait pourquoi on optimise, on a une bonne raison de rendre la fonction moins lisible et on peut donner le gain réel qui a été obtenu.

Parfois, ce n’est même pas en optimisant son code qu’on gagne en vitesse. Le dernier exemple en date a été une bonne leçon pour moi… Dans Ryzom, on a voulu changer la stl, plutôt que de prendre celle par défaut de Visual Studio 9, on a essayé STLport, pour voir. A la base c’était surtout pour tester en mode Debug et voir s’il détectait des problèmes au runtime (ce que STLport fait très bien).
En Release, on s’est rendu compte que le client Ryzom tournait 25% plus vite. C’est ENORME quand on sait que l’on a juste changé une librairie.

Alors s’il vous plaît, arrêtez de vous prendre la tête avec vos boucles for et autres optimisations locales et, si vous voulez vraiment optimiser, concentrez vous réellement sur ce qui rame.

How to remove permanently a file from subversion (SVN) repository

When you do a svn delete, in fact the file stays in the repository history and can be restored.

I had to remove some directories from a repository because they were too big so I’ll share you the process I used:

First, you need to connect to the server where the svn repository is.

We dump the repository into a file called svn_full. It’s a text file that contains all information of the repository :
svnadmin dump /PATH/TO/THE/REPOSITORY > svn_full

Then we have to filter the dumped file to remove directories you want to remove. Read this page to understand the parameters.
cat svn_full | svndumpfilter exclude --drop-empty-revs --renumber-revs /trunk/PATH1/ /trunk/PATH2/ > svn_filtered 2>log

Now the svn_filtered files is the dump files without the paths trunk/PATH1/ and /trunk/PATH2/. Check the log file that was generated to see if all files was removed as you wanted.

We have to remove the old repository:
mv /PATH/TO/THE/REPOSITORY /PATH/TO/THE/REPOSITORY_BACKUP

Create a new empty repository:
svnadmin create /PATH/TO/THE/REPOSITORY

Fill the repository with the new files:
svnadmin load /PATH/TO/THE/REPOSITORY < svn_filtered >log

Check the generated log file to see if everything want right.

You should finally copy the old config and hook files from old repository to the new one:
cp /PATH/TO/THE/REPOSITORY_BACKUP/conf/* /PATH/TO/THE/REPOSITORY/conf/
cp /PATH/TO/THE/REPOSITORY_BACKUP/hook/* /PATH/TO/THE/REPOSITORY/hook/

All your users have to checkout the new repository with a svn checkout command.

That’s all :-)

Finally, if you use redmine like me, you want redmine to update the new repository. You just have to clear (not drop) the following sql tables:
changes, changesets and changesets_issues. The next time someone will click on the “Repository” tab in redmine, it’ll regenerate all entries. If it’s too long and you have a timeout, go to the redmine directory and run the following command line to force the generation of the changes:
ruby script/runner "Repository.fetch_changesets" -e production

Les égouts et les couleuvres

L’autre jour, je suis tombé sur le ticket d’un programmeur qui montrait un bout de son code, le voici :

bool SIM_FoodMemory::IsBoringChoice(std::string foodname)
{
	//return true if we ate the same food more than once in the last 14 days
	int recentmatches = 0;
	iter it;
	for(it = Items.begin(); it != Items.end(); ++it)
	{
		SIM_FoodMemoryItem* pitem = *it;
		if(pitem->FoodName == foodname)
		{
			int diff = SIM_GetSim()->GetTurnCount() - pitem->TurnConsumed;
			if(diff < 14)
			{
				recentmatches++;
			}
		}
	}

	if(recentmatches > 1)
	{
		return true;
	}
	else
	{
		return false;
	}
}

Autant vous le dire tout de suite, ce bout de code ne me plait pas du tout. Si l’on fait une remarque sur ce genre de code, beaucoup de coders sortent leur carte anti-troll ; « les goûts et les couleurs, ça ne se discute pas » ainsi que l’autre carte souvent utilisée ; « pas grave, ce n’est pas ‘time critic’ ».

Mais est-ce que ces 2 cartes devraient tout autoriser ? Alors sous ces 2 prétextes, on peut faire tout et n’importe quoi ? Je suis désolé, mais je ne suis pas d’accord.

Prenons le cas de la classe string qui est passé en paramètre, on est loin du débat « est-ce que ++i est mieux que i++ » ! Même si la fonction n’est pas ‘time critic’, passer une classe string comme cela entraine non seulement un overhead de code mais surtout, des allocations/désallocations mémoire! Il faut allouer la classe sur la pile, appeler le ctor, allouer la mémoire pour la chaine de caractère, la copier, et, à la fin, la désalouer. En plus de prendre du temps CPU, ça fragmente la mémoire, ce qui peut avoir des effets réellement génants.

Maintenant le fait de ne pas utiliser de const ? Mettre un header SIM_ au lieu d’utiliser un namespace ? Ce code montre très clairement que c’est un ancien programmeur C et qu’il ne maitrise pas des concepts basiques du C++ et continue à utiliser ses anciennes habitudes du C.

Tout le monde fait des erreurs, tout le monde a tendance à faire comme il a toujours fait, c’est humain. Ce qui me dérange plus, c’est la réaction, plutôt que d’accepter qu’il y ait un problème et le corriger, le programmeur se réfugie derrière le “c’est pas grave, les gouts et les couleurs… c’est pas time critic…”. Comment évoluer, s’améliorer dans ces conditions ?

Pour finir, voilà comment, moi, j’aurais surement écrit ce bout de code (ce n’est pas du tout pour montrer la solution parfaite, mais juste pour jouer le jeu) :

using namespace std;
namespace SIM {
bool FoodMemory::isBoringChoice(const string &foodname) const
{
	int recentmatches = 0;
	for(iter it = Items.begin(); it != Items.end(); it++)
	{
		FoodMemoryItem *item = *it;
		if(item->FoodName == foodname)
		{
			int diff = sim()->turnCount() - item->TurnConsumed;
			if(diff < 14) recentmatches++;
		}
	}

	return (recentmatches > 1);
}

J’ai clairement un style plus compact, j’ai fait exprès de ne pas changer l’algo sinon j’aurais fait directement un return dans le if pour casser la boucle ‘for’ le plus tôt possible.

Et comme je suis curieux, vous l’auriez écrit comment vous ?

←Older