Règles de codage de Scribus

Règles de codage de Scribus

Document initial : Paul F. Johnson, 26 fév. 2004, version : 0.04
Révisé par Craig Ringer le 16 août 2005, version : 0.05 (encodage du texte, exceptions, à qui envoyer les mises à jour)
Révisé par Peter Linnell le 06 sept. 2005, version : 0.06 (éléments d'encodage de texte ajoutés pour tenir compte des des entretiens récents)

Ce document se veut un guide pour tous les développeurs et contributeurs du projet Scribus. Il s'agit d'un outil convivial qui aide à préserver la simplicité du code source et à en assurer la maintenance.

Documentation du code

Tout le code doit être documenté clairement.

Avant d'utiliser une méthode en particulier, les éléments suivants doivent être présents :

/*!
 \fn functionName::functionName()
 \author Nom des auteurs
 \date
 \brief Constructeur pour la classe
 \param Aucun
 \retval Aucun
 */

Vous devez mettre en place quelque chose de semblable avant d'établir les définitions de classe.

De préférence, tous les détails devraient rédigés être en anglais. Cela inclut les commentaires.

À moins qu'un commentaire n'occupe une seule ligne, vous devez utiliser // de préférence à /* */.

Code

Convention de noms de variable

Tous les noms de variable doivent commencer en lettres minuscules. Si le nom comporte plusieurs parties, la deuxième partie doit commencer par une lettre majuscule. Par exemple : int foo et int fooBar. Sous aucune circonstance, vous ne devez utiliser le caractère de soulignement (_).

Les variables doivent être nommées clairement. La seule exception réside dans une boucle for. De préférence, les variables doivent être libellées en anglais. Nous avons entrepris de convertir et de modifier les variables existantes en fonction de ce modèle.

Les variables temporaires (comme celles utilisées dans une boucle for ou celles qui dépendent d'un contexte)doivent être déclarées uniquement lorsqu'elles sont nécessaires et si elles ne sont pas utilisées dans un contexte global. S'il y a une condition au début d'une méthode, toutes les variables nécessaires doivent être déclarées, mais rien d'autre. Par exemple :

void foo::bar(f *b)
{
 int j;
 string g = b->passed_in;
 double fg, m, c;
 which_string(g, &j);
 if (j == true)
  return;
 // rest of the code
}

ne doit pas avoir de déclaration de variables doubles. On peut se demander si g doit être déclarée, mais dans cet exemple, cela n'a pas d'importance.

Si une variable n'a pas d'utilité, ne la créez pas. Dans l'exemple ci-dessus, rien n'exige l'utilisation de g.

Convention de tabulation

  1. Définitions à l'intérieur d'une classe

    À l'exception des déclarations d'accès, vous devez placer une tabulation avant chaque définition de méthode et de variable. Il ne doit pas y avoir de tabulation avant les déclaration d'accès. Il y a une tabulation avant Q_Object.

  2. Dans le code principal :

    Il doit y avoir une tabulation au début de chaque ligne. Sur toutes les lignes conditionnelles, il doit y avoir une accolade ouvrante seule. Sur les lignes suivantes, il doit y avoir une autre tabulation avant le code. L'accolade fermante doit être alignée sous l'accolade ouvrante.

Conditions

Construisez toujours des conditions logiques. Cela peut paraître simpliste, mais observez l'exemple qui suit :

int foobar(bool m)
{
 if (m == true)
	{
  somethingA(1);
  somethingB(1);
  somethingC(1);
	}
 else
	{
  somethingA(0);
  somethingB(0);
  somethingC(0);
	}
}

Voici comment on peut rendre les choses plus claires :

int foobar(bool m)
{
   int c = m == true ? 1 : 0;
   somethingA(c);
   somethingB(c);
   somethingC(c);
}

Cette présentation est beaucoup plus lisible et sera généralement plus facile à optimiser.

Utilisez des conditions ternaires (?) au lieu de if / else. Elles sont beaucoup plus rapides à exécuter une fois que le code est compilé.

Selon la tâche à accomplir, il vaut mieux éviter les énoncés switch. Il existe souvent une manière plus simple de procéder. Par exemple :

int b;
string s;
switch (f)
{
 case 1 :
  b = 4;
  s = "four";
  break;
 case 2 :
  b = 8;
  s = "eight";
  break;
 case 3 :
  b = 12;
  s = "twelve";
}

peut être ré-écrit comme ceci :

int b = f * 4;
char *number[] = {"four", "eight", "twelve"};
size_t no = sizeof(number) / sizeof(*number);
s = number(f);

Le switch a disparu et le code généré tourne beaucoup plus vite; comme les conditions n'ont pas besoin d'être créées, il sera mieux optimisé. Note : avec la fonction QObject::tr() , il se peut que char* ne fonctionne pas toujours correctement. Une solution de rechange consiste à utiliser QString number[] = {"four", "eight", "twelve"}; etc.

Évidemment, vous ne pouvez utiliser le code ci-dessus pour des conditions switch plus complexes.

Formatage des conditions

Le formatage devrait ressembler à ceci :

if (fred == 5)
{
 // something
 // something
}

Le formatage du switch doit ressembler à ceci :

switch (f)
{
 case 0:
  if (fred == 1)
		{
   // something
   // more
   // ok
		}
  else
		{
   if (jimmy == 0)
			{
    // hi
    // code
			}
   // something here
		}
  break;
}

Le code est facile à lire et on peut déterminer aisément où la condition prend fin.

N'écrivez jamais, sous AUCUNE considération, une ligne telle que celle-ci :

if (foo == bar) { somethingA(0); }

Employez plutôt cette présentation :

if (foo == bar)
 somethingA(0);

Si les accolades ne sont pas requises, ne les utilisez pas. Par exemple, les accolades ne seraient pas requises ici

for (int j = 0; j < 10; ++j)
 cout << j << "\n"?;

Préincrémentation et postimcrémentation

Dans les boucles, vous devez choisir la préincrémentation (++a plutôt que a++). Avec les processeurs ARM et x64 (vérifier à pour ia64) ++a requiert la moitié moins de cycles d'horloge que le style postfix. Dans certaines circonstances, ++a est beaucoup plus efficace sous ia32. Cela est vrai même avec des itérateurs. La postincrémentation doit être utilisée dans le genre de code ci-dessous :

if (b == true)
{
 m->wibble(c);
 m->wobble(c++);
}

Espacements

Il doit y avoir un espace :

Utilisation de modèles

Il convient d'utiliser les modèles pour les types et les classes. Les responsables du projet recommandent d'utiliser g++ 3.x, qui supporte parfaitement les modèles et les classes modèles. N'oubliez pas la charge de calcul supplémentaire due à l'utilisation des modèles!

Lorsque vous utilisez des modèles, rappelez-vous ce qui suit :

Optimisation

À moins de savoir exactement ce que vous faites et, surtout, ce que le code fait, n'essayez pas d'optimiser le code. À beaucoup d'égards, l'optimisation est davantage un art qu'une science.

Du code comme a = b = c = 0; est plus efficace (lorsqu'il est traité par le compilateur) que a=0; b=0; c=0;

Il vaut mieux se concentrer sur la manière dont le code fonctionnne plutôt que d'essayer une des "astuces" du métier.

Tout changement à grande échelle doit être approuvé au préalable. Parfois, un changement minime peut avoir des répercussions très importantes.

Des lignes séparées contenant des appels aux méthodes Qt sont beaucoup moins efficaces que des appels regroupés dans une boucle for.

Initialisation

QString fred = QString("bob") n'est pas la même chose que QString fred("bob"). Le premier crée un élément temporaire qui est ensuite abandonné. Cette technique est lente et doit être évitée.

Dans les constructeurs, il est préférable d'utiliser ce qui suit :

Fred::Fred() : ParentClass(), argument1("initialValue"), argument2(INITIAL_VALUE) { }

que

Fred::Fred() : ParentClass() { argument1 = "initialvalue"; argument2 = INITIAL_VALUE; }

pour les raisons expliquées plus haut. Il vaut mieux initialiser les membres dans l'ordre où ils sont listés dans la déclaration de la classe.

Vérification de la mémoire

Comme les exceptions ne sont pas disponibles (voir ci-dessous), nous utilisons Q_CHECK_PTR pour vérifier les allocations. Cela signifie que vous obtenez un abandon avec un message explicatif plutôt qu'un segfault sans aucun détail.

int *m = new int[2048]; Q_CHECK_PTR(m);
Exceptions

Idéalement, tous les appels à new doivent être capturés. Cette fonctionnalité n'existe pas dans les versions courantes. La méthode souhaitable serait :

#include <new>

try
{
  m = new int[2048];
}

catch(bad_exception)
{
  cout << "memory allocation to m failed - <method name<" << endl;
  exit(EXIT_FAILURE);
}

Comme il y a plusieurs appels à new dans Scribus, ceux-ci devront être encapsulés.

Les utilisateurs de Qt qui compilent eux-mêmes le programme doivent inclure dans leur code le script ./configure, qui doit lever une exception C++. Sinon, les catch n'intercepteraient rien et Scribus ne s'exécuterait pas.

En ce moment (pour v3.2 - v3.4), KDE recommande que Qt soit compilé sans exceptions. La suggestion est mauvaise, puisqu'elle signifie que les exceptions pour allocation de mémoire erronée ne seront pas interceptées à l'aide de la méthode choisie. Comme la plupart des distributions suivent cette recommandation, cela signifie que nous (de Scribus) devrions nous priver des exceptions.

Encodage du texte

L'encodage du texte doit être traité avec soin. Si vous traitez du texte UTF-8 comme du latin-1, par exemple, vous risquez d'obtenir des incohérences et des erreurs. Ces problèmes sont souvent difficiles à repérer, d'où la nécessité d'écrire le code avec vigilance. Les détails de l'encodage du texte ne sont pas abordés ici, mais vous trouverez quelques précisions ailleurs dans la documentation. La partie qui suit consiste en une version antérieure du texte; vos commentaires sont bienvenus.

La première chose que vous devez comprendre est que les QString de Qt, et la plupart des autres classes de Qt, connaissent leur propre encodage et prennent soin des questions de l'encodage du texte pour vous, pour qu'ils puissent être traités comme "seulement du texte" lorsque passés dans Scribus ou à d'autres classes Qt. C'est uniquement à l'interface entre le code orienté octet et le code orienté "texte" qu'un soin particulier est requis. Ces interfaces peuvent ne pas être évidentes à trouver (la conversion implicite d'un QString à un QCString ou char*, par exemple, est facilement manquée) donc il faut lire avec attention et précaution la documentation de l'API.

Bien qu'il n'y ait pas de réel substitut à la compréhension des encodages de texte, voici quelques règles importantes à suivre:

Construisez toujours un QString à partir d'un octet en utilisant une des méthodes QString::fromblah(...) . Vous ne devriez jamais utiliser QString(char*) ou QString(QCString). Vous devez déterminer dans quel encodage est la chaîne d'octets avec laquelle vous travaillez et utiliser l'appel correct, en examinant la documentation du système/librairie que vous appelez. Il n'y a généralement aucun moyen de dire quel est l'encodage de la chaîne en l'examinant - au mieux il s'agit de deviner. N'assumez pas qu'une chaîne est en utf-8 ou latin-1, parce que même si ça fonctionne pour vous, avec certains langages ce ne sera pas le cas. Pour lire des données à partir du système d'exploitation ou de librairies C, vous utiliserez en général QString::fromLocal8Bit(...). En cas de doute, demandez à quelqu'un.

De manière similaire, vous devriez toujours utiliser les méthodes de conversionQString appropriées pour convertir des QString en chaînes d'octets (par exemple QCString, QByteArray, ou char*). Notez que de telles conversions peuvent survenir automatiquement, donc surveillez les conversions implicites. Pour envoyer des données aux appels systèmes ou librairies C, vous utiliserez en général QString::local8Bit() mais vous devez vérifier et confirmer que c'est adapté à l'appel spécifique que vous effectuez. Certains appels win32, par exemple, attendent des données UCS-2 (wchar), et certaines librairies C attendent des chaînes UTF-8 comme arguments à certaines méthodes.

Vous ne devez absolument jamais écrire ceci :

QString blah = anotherQString.utf8();

Le code ci-dessus:

Donc, vous finissez par effectuer beaucoup de travail inutile d'encodage et de décodage de la chaîne, et de plus il y aura probablement corruption en décodant une chaîne utf-8 comme latin-1. Habituellement, vous ne le noterez pas, parce que vous envoyez probablement uniquement des données ASCII, et ainsi le bug sera découvert beaucoup plus tard lorsque des personnes dans d'autres langages utilisent d'autres encodage de texte. Ces questions tendent à être difficiles à dépister.

Pour des raisons similaires, lorsque vous lisez ou écrivez un QTextStream, rappelez-vous d'appeler QTextStream::setEncoding(...) avant de lire/écrire toute donnée. Utilisez l'opérateur<< avec lesQString, mais WriteRawBytes(...) pour écrire des chaîne d'octets qui sont déjà dans l'encodage correct. Les chaînes d'octets dans d'autes encodages doivent être converties à QString ou transcodées en utilisant QTextCodec. N'assumez pas que le QTextStream écrira en UTF-8. L'encodage correct dépend de l'endroit où les données vont et d'où elles viennent. Par exemple, un fichier texte seul que l'utilisateur édite devrait être dans l'encodage qui convient à sa langue. Si vous n'êtes pas sûr, demandez à quelqu'un.

Ne convertissez pas un QString en une chaîne d'octets lorsque vous écrivez dans un QTextStream - le QTextStream le fait pour vous avez l'opérateur<<. Par exemple:

QTextStream ts(&file); // Dans ce cas, nous voulons écrire de UTF8 dans ce fichier, pas écrire dans l'encodage local une chaîne d'octets 8 bits. ts.setEncoding(QTextStream::UnicodeUTF8); ts << someQString.utf8();

Il s'agit de la même erreur que dans l'exemple précédent, mais écrit d'une manière différente. À la place, vous devriez omettre le .utf8().

La meilleure manière de gérer les encodages de texte est de les convertir en chaînes d'octets 8 bits en QString ou une autre classe "sécuritaire" aussitôt que possible à l'entrée, et de convertir en chaînes d'octets 8 bits le plus tard possible à la sortie. Par-dessus tout, ne le faites qu'une seule fois et surveillez les conversions implicites. Vous devez penser aux encodages en passant du texte à travers toute interface qui demande des chaînes orientées octets plutôt que des QStrings. Rappelez-vous de lire la documentation des API que vous utilisez pour déterminer comment elles se comportent face à l'encodage du texte, et si vous n'êtes pas sûr, demandez à quelqu'un.

Il est fortement recommandé de lire http://doc.trolltech.com/3.3/i18n.html pour de plus amples informations sur cette question, en particulier la section "Support for Encodings". Vous devriez également lireAchtung! Binary and Character data.

Indices pour la traduction - Questions d'encodages

Veuillez utiliser:

 "<qt>" + tr("text for translation") + "</qt>"

et non pas ceci:

 tr("<qt>text for translation</qt>")

qui est réellement ennuyeux pour les traducteurs.

Si vous écrivez une classe qui n'hérite pas de QObject ou d'une classe Qt, vous ne pouvez pas utiliser la méthode héritée tr(). Si vous utilisez quelque chose comme suit:

 QObject::tr("text for translation")

remplacez-le par:

QObject::tr("text for translation", "describe what is it related")

Un exemple du scripteur:

QObject::tr("Item is not text frame", "scripter error")

Cela aide les traducteurs à situer le contexte et leur indique également ce qu'ils traduisent, même si dans certains cas les chaînes sont génériques de nature.

Il y a un autre point concernant les traductions, même s'il concerne plus l'encodage correct, etc.: veuillez ne pas utiliser:

tr( QString("blah %1").arg(something) )
ou sinon utilisez un QString à l'intérieur d'un tr(...) . Le faire causerait une conversion implicite en 'const char*', que le QString convertirait en une chaîne d'octets latin-1. Si des caractères ne peuvent pas être représentés en latin-1, ils sont remplacés par '?'. L'usage correct est:
tr("blah %1","context").arg("something")

... en exploitant le fait que tr(...) retourne un QString et que vous pouvez enchaîner les appels à QString::arg. Cela évite toute conversion implicite et évitera toute une série de bug d'encodages bizarres.

Si vous devez réellement traduire du texte qui ne peut pas être représenté en latin-1, vous devez utiliser QObject::trUtf8(...). Si vous voulez traduire une chaîne avec "?" (delta majuscule) à l'intérieur, par exemple, vous pourriez essayer:

tr("? is %1", "context")

ce qui est faux, non portable, peut ne pas compilé, ou pire peut donner des résultats différents sur des systèmes différents. Il faut utiliser des chaînes de caractères échappés au lieu d'inclure un caractère Unicode ou latin-1 étendu directement dans la source. La séquence d'octets UTF-8 pour ce caractère est \xce\x94, donc il faut essayer:

tr("\xce\x94 is %1", "context")

mais c'est également faux, parce que la séquence d'octets Unicode sera interprétée comme du latin-1 et vous obtiendrez un résultat corrompu. À la place, vous devez utiliser:

 trUtf8("\xce\x94 is %1", "context")

De même, si vous voulez inclure le symbole © (copyright), qui existe aussi en latin-1, vous pouvez utiliser au choix:

tr("something \xa9 someone", "context")

ou

trUtf8("something \xc2\xa9 someone", "context")

Portabilité

Ne convertissez pas un pointeur en entier (int), cela cause des problèmes de portabilité où sizeof(int) != sizeof(void*). Vous devriez chercher une autre méthode pour atteindre le résultat souhaité.

Soumettre les mises à jour

Les mises à jour du code doivent être envoyées à Franz, Craig Bradney, ou Paul. Les mises à jour de la documentation doivent être envoyées à Peter et Craig Bradney. Cela est sujet à changement. Les questions de scripting doivent être adressées à Petr et Craig Ringer.

Les mises à jour doivent être en texte seul. diff -u old_file.cpp new_file.cpp >difference.diff est la meilleure manière de les envoyer. Veuillez ne pas envoyer pas de mises à jour à la liste principale. Rappelez-vous d'utiliser -u (diff unifié) parce que les autres types de diff sont difficiles à utiliser.

Veuillez vous assurer lorsque vous avez changé quelque chose de mettre un commentaire juste avant le changement. Assurez-vous de mettre la date, l'auteur du changement et la raison de ce changement. Vérifiez que les commentaires incluent la ligne de code originale. Avec le temps, elles seront supprimées, mais il est utile de voir ce qui a été altéré.

Envoyez un seul diff pour chaque fichier modifié. Cela rend la soumission des changements beaucoup plus simple pour nous!

Écrire des extensions

Veuillez utiliser les fonctions actuelles et vous référer à Plugin howto pour plus d'information.

Tout code appelé à partir de l'application Scribus principale doit être entourée avec un extern "C" { ... }; pour permettre à l'extension de fonctionner. Assurez-vous que tout le code est documenté et suit les directives spécifiées plus haut.

m