Désolé pour la manière dont les choses ont été lents ici dernièrement, je suis encore souffrant de retard de travail WWDC, plus j'ai passé beaucoup de temps sur le nouveau livre. C'est le premier livre que j'ai essayé d'écrire tout en faisant travailler le client à temps plein et cela prend un peu d'un péage sur moi.
J'ai eu un billet de blog flottant autour de ma tête depuis l'écriture Début de développement iPhone qui n'a jamais devenu complètement formé. C'est sur la façon dont le processus d'écriture sur le code a changé la façon dont je écrire du code. Je pense que les pensées sont enfin prêts à se figer dans une forme solide. Alors, voilà.
L'autre jour, j'ai vu quelqu'un porter un t-shirt qui disait: «La danse comme regarder personne». Je ne suis pas beaucoup plus d'un danseur, mais j'aime bien le sentiment. Toutefois, la danse n'est pas de codage. La pire chose que vous pouvez faire est de code comme regarder personne. Vous devriez le code comme tout le monde vous regarde.
J'ai remarqué une chose, après que j'avais été à écrire du code pour la consommation publique pendant un moment, c'est que je suis devenu beaucoup plus critique de mon propre code que je l'étais avant que j'ai commencé à mettre là-bas pour le monde de voir. Beaucoup de qui vient de pure vanité - je veux minimiser le nombre d'erreurs stupides que les gens vont m'appeler sur. Il ya beaucoup d'yeux sur le code que j'écris des livres et pour ce blog, et certains de ces yeux appartiennent à des gens qui sont, franchement, plus intelligents que moi.
Il est impossible de toujours mettre hors code parfait, mais je ne veux pas donner l'impression d'être un codeur bâclé ou, pire, d'être un hack complet. Le processus que je aller jusqu'au bout de code qui va dans un poste de livre ou d'un blog va quelque chose comme ceci:
Je pensais que je serais revenir à mon ancienne façon quand je n'étais pas à écrire du code pour la consommation publique, mais il s'avère que, une fois acquise, cette habitude est difficile à ébranler. Franchement, comme la plupart des développeurs, j'avais toujours supposé que la troisième étape, ou au moins les aspects les plus Picayune de celui-ci, ont été une perte de temps lorsque vous faites le travail de production.
Cela n'a pas s'est avéré être vrai dans mon expérience. En fait, ça a été tout le contraire. Dans presque tous les cas, je passe moins de temps à écrire de code parce que mon code est maintenant plus lisible, plus précis, et généralement un peu plus courte que la façon dont j'avais l'habitude d'écrire du code. Chaque fois que je dois changer, corriger, ou d'étendre le code existant, je passe moins de temps à le faire, et ceux des petits bouts ajouter jusqu'à au cours de même un emploi court. Sur le long journal, toutes ces petites choses énormes dividendes.
Voici un exemple que je travaillais sur ce week-end pour OpenGL ES 2.0 pour iOS 4. Je suis rassembler une classe Objective-C pour rendre la création et la gestion de plusieurs programmes et les shaders OpenGL facile. Un morceau de fonctionnalité que je voulais inclure était la capacité d'obtenir le programme et les journaux de shaders de OpenGL. Sans cette information, il est extrêmement difficile de déterminer pourquoi un shader ne sera pas compiler ou un programme ne sera pas un lien. Le résultat final de mes deux premières étapes («Faites du travail», «Faites bien travailler»), était le suivant:
Il était évident, même si j'ai écrit ce code que je n'allais pas le laisser comme ça. Vous pouvez vous en sortir avec ce genre de duplication inutile de code dans la plupart des projets, car il fonctionne bien et la plupart des clients et des gestionnaires de développement beaucoup n'ont pas les côtelettes technique pour lire le code, mais il viole les DRY quelque chose de principe féroce. Ce n'est guère le pire exemple de copier-coller de codage que j'ai jamais vu, mais ce type de code ne devrait jamais entrer en production.
J'ai testé ce code pour vous assurer des méthodes de travail, alors j'ai immédiatement refactorisé les deux méthodes log shaders en ajoutant une méthode privée déclaré dans une extension. J'ai ensuite changé les deux méthodes existantes pour simplement appeler la méthode privée, donc j'ai été, puis en utilisant le même code pour les deux scénarios.
C'est certainement mieux que la version originale. Je me suis débarrassé de la duplication évidente entre les deux méthodes presque identiques log shader. La seule différence entre les deux méthodes originales a été le GLuint (entier non signé) la valeur passée dans les appels OpenGL ES deux fonctions.
Ce code me dérangeait encore, cependant. Regardez comment le shader similaires et la fonctionnalité journal de programme est. Il est identique sauf pour les deux appels de fonction et, j'ai réalisé, ces deux appels de fonction prendre exactement les mêmes paramètres. Il avait été un moment depuis que je les avais utilisés, de sorte qu'il a fallu que mon cerveau quelques secondes de trimer jusqu'à combien pointeur fonction C travaillé, mais une fois qu'il a fait, le chemin pour la refactorisation de ce code est devenu évident. Une seule méthode peut être utilisée pour tous les trois de ces cas, si j'ai pris des pointeurs de fonction de l'API OpenGL deux appels comme paramètres de méthode.
Or, cette seconde refactorisation est un que beaucoup de gens diraient probablement ne vaut pas la peine. C'est trop. Il ne passe pas une «analyse coûts-avantages». Le résultat ne va pas être que beaucoup plus courte, alors comment pouvez-vous justifier le temps? J'ai l'habitude de penser de la même façon avant de dépenser autant de temps à écrire du code pour la consommation publique, mais maintenant je suis devenu convaincu que la refactorisation comme celui-ci ne passent à l'analyse coût-bénéfice. Je n'étais pas mettre assez de poids sur la valeur du bon code lorsque vous faites cette analyse. Lorsque vous revenez à quelques semaines de code, mois ou années plus tard, vous ne serez pas se souvenir de tous les petits détails. Avec bien écrit le code, vous n'aurez pas à.
La vérité (dans mon expérience, au moins) est que le code refactorisé est assez facile à maintenir que cela vaut la peine de prendre le temps de le faire. Comme tout, plus vous le faites, mieux vous mettre au travail, et plus vite vous pouvez le faire. Finalement, il devient une seconde nature. Vous finissez par écrit un meilleur code à peu près la même quantité de temps que vous avez utilisé pour écrire du code bâclé, mais fonctionnel. De plus, chaque fois que vous visitez ce code dans l'avenir, vous avez un code un peu moins de chercher à travers. Chaque fois que vous avez à déboguer ce code, vous avez un code un peu moins pour passer. Chaque fois que vous corriger le code, vous avez moins de code à corriger.
Si j'avais une erreur dans la version originale de ces fonctions log, je aurait presque certainement la même erreur exacte en trois endroits. Quand j'ai découvert cette erreur, tu crois que je ne me souviens de le fixer dans les trois endroits? Peut-être, si j'ai écrit le code récemment suffit de se souvenir. Si quelqu'un d'autre fixaient mon code, pensez-vous qu'ils sachent qu'ils avaient de le fixer à trois endroits? Peut-être ... mais plus probablement ils ne seraient pas.
Voici la version définitive de la méthode que j'ai installés sur le livre ^ 1:
Maintenant, si vous travaillez pour quelqu'un d'autre, surtout si vous travaillez dans une grande société, vous pouvez être mesurée sur une série de paramètres, y compris quelque chose comme "lignes de code écrites» (LOC). Bien que je comprenne la nécessité de la responsabilisation, la plupart des entreprises sont métriques (pour être franc) d'un abus de conneries de la statistique. La métrique absolue pire n'est jamais imaginé LOC. Si vous travaillez quelque part qui est encore dans le codage âges sombres et vous mesurer par LOC, courir comme un diable, ou au moins faire pression pour un changement de cette politique. Mesurer la performance basée sur le LOC encourage même de bons ingénieurs pour écrire bâclée, code horrible.
Dans mon exemple, j'ai passé une partie de mon temps à améliorer mon code par enlever 25 lignes de code. J'ai passé beaucoup de temps de le prendre à partir de 64 lignes de code à 39 lignes de code. En tant que développeur, je ne devrais pas être puni pour faire ça!
Dans des situations réelles, nous avons souvent nous convaincre qu'il n'y a pas de temps pour la refactorisation de code pendant que nous. Après tout, nous devons faire ce délai, et il ya always une date limite! Nous pouvons toujours revenir et factoriser le code plus tard quand nous avons plus de temps, non?
Seul vous ne serez pas. Vous ne reviendra pas jusqu'à ce que vous devez absolument, et par ce point, il serait probablement plus rapide de le réécrire. Si vous travaillez pour quelqu'un d'autre, ils sont peu susceptibles de vous laisser aller en arrière et passer du temps "fixer" quelque chose qui fonctionne parfaitement bien. C'est particulièrement vrai si vous avez un gestionnaire de non-techniques. En outre, vous serez probablement sur votre prochaine échéance déjà et la pression du temps se sentir à nouveau.
À long terme, le code de bonnes vaut le temps qu'il faut. Souvent, ça vaut le coup dans le court terme aussi bien.
Prenez le temps de remanier, et non après le fait qu'il fasse partie de votre processus de développement. Faites-le d'emblée, avant qu'il ne passe à l'essai, parce que vous ne reviendront pas. Relisez votre code avant de vous envoyer un build.
Ou, en d'autres termes, le code comme si chacun était capable de voir et de comprendre votre code.
Il ya un autre point que je veux faire. Non seulement devez-vous le code comme s'il y avait beaucoup de yeux sur votre code, vous devriez réellement obtenir beaucoup de regard sur votre code si vous le pouvez. Vous ratez des choses même si vous faites la relecture et la refactorisation partie de votre processus de développement. Vous aurez même parfois manquer des trucs stupides. Affaire au point, j'ai reçu un e-mail peu de temps après la soumission de ce à partir Brent Simmons of NetNewsWire renommée. Brent très gentiment pris le temps de relire le code et fait remarquer un bogue et plusieurs améliorations qui pourraient être apportées.
Voici ses suggestions:
Merci, Brent!
1 Cela peut, bien sûr, le changement si mon relecteur technique détecte un problème avec elle
J'ai eu un billet de blog flottant autour de ma tête depuis l'écriture Début de développement iPhone qui n'a jamais devenu complètement formé. C'est sur la façon dont le processus d'écriture sur le code a changé la façon dont je écrire du code. Je pense que les pensées sont enfin prêts à se figer dans une forme solide. Alors, voilà.
L'autre jour, j'ai vu quelqu'un porter un t-shirt qui disait: «La danse comme regarder personne». Je ne suis pas beaucoup plus d'un danseur, mais j'aime bien le sentiment. Toutefois, la danse n'est pas de codage. La pire chose que vous pouvez faire est de code comme regarder personne. Vous devriez le code comme tout le monde vous regarde.
J'ai remarqué une chose, après que j'avais été à écrire du code pour la consommation publique pendant un moment, c'est que je suis devenu beaucoup plus critique de mon propre code que je l'étais avant que j'ai commencé à mettre là-bas pour le monde de voir. Beaucoup de qui vient de pure vanité - je veux minimiser le nombre d'erreurs stupides que les gens vont m'appeler sur. Il ya beaucoup d'yeux sur le code que j'écris des livres et pour ce blog, et certains de ces yeux appartiennent à des gens qui sont, franchement, plus intelligents que moi.
Il est impossible de toujours mettre hors code parfait, mais je ne veux pas donner l'impression d'être un codeur bâclé ou, pire, d'être un hack complet. Le processus que je aller jusqu'au bout de code qui va dans un poste de livre ou d'un blog va quelque chose comme ceci:
- Faire fonctionner
- Faire le travail bien
- Faire bien lu
Je pensais que je serais revenir à mon ancienne façon quand je n'étais pas à écrire du code pour la consommation publique, mais il s'avère que, une fois acquise, cette habitude est difficile à ébranler. Franchement, comme la plupart des développeurs, j'avais toujours supposé que la troisième étape, ou au moins les aspects les plus Picayune de celui-ci, ont été une perte de temps lorsque vous faites le travail de production.
Cela n'a pas s'est avéré être vrai dans mon expérience. En fait, ça a été tout le contraire. Dans presque tous les cas, je passe moins de temps à écrire de code parce que mon code est maintenant plus lisible, plus précis, et généralement un peu plus courte que la façon dont j'avais l'habitude d'écrire du code. Chaque fois que je dois changer, corriger, ou d'étendre le code existant, je passe moins de temps à le faire, et ceux des petits bouts ajouter jusqu'à au cours de même un emploi court. Sur le long journal, toutes ces petites choses énormes dividendes.
Voici un exemple que je travaillais sur ce week-end pour OpenGL ES 2.0 pour iOS 4. Je suis rassembler une classe Objective-C pour rendre la création et la gestion de plusieurs programmes et les shaders OpenGL facile. Un morceau de fonctionnalité que je voulais inclure était la capacité d'obtenir le programme et les journaux de shaders de OpenGL. Sans cette information, il est extrêmement difficile de déterminer pourquoi un shader ne sera pas compiler ou un programme ne sera pas un lien. Le résultat final de mes deux premières étapes («Faites du travail», «Faites bien travailler»), était le suivant:
- (NSString *)vertexShaderLog
{
GLint logLength = 0, charsWritten = 0;
glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(vertShader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}
- (NSString *)fragmentShaderLog
{
GLint logLength = 0, charsWritten = 0;
glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(fragShader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}
- (NSString *)programLog
{
GLint logLength = 0, charsWritten = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
glGetProgramInfoLog(program, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}Il était évident, même si j'ai écrit ce code que je n'allais pas le laisser comme ça. Vous pouvez vous en sortir avec ce genre de duplication inutile de code dans la plupart des projets, car il fonctionne bien et la plupart des clients et des gestionnaires de développement beaucoup n'ont pas les côtelettes technique pour lire le code, mais il viole les DRY quelque chose de principe féroce. Ce n'est guère le pire exemple de copier-coller de codage que j'ai jamais vu, mais ce type de code ne devrait jamais entrer en production.
J'ai testé ce code pour vous assurer des méthodes de travail, alors j'ai immédiatement refactorisé les deux méthodes log shaders en ajoutant une méthode privée déclaré dans une extension. J'ai ensuite changé les deux méthodes existantes pour simplement appeler la méthode privée, donc j'ai été, puis en utilisant le même code pour les deux scénarios.
- (NSString *)shaderLogForShader:(GLuint)shader
{
GLint logLength = 0, charsWritten = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(shader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}
- (NSString *)vertexShaderLog
{
return [self shaderLogForShader:vertShader];
}
- (NSString *)fragmentShaderLog
{
return [self shaderLogForShader:fragShader];
}
- (NSString *)programLog
{
GLint logLength = 0, charsWritten = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
glGetProgramInfoLog(program, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}C'est certainement mieux que la version originale. Je me suis débarrassé de la duplication évidente entre les deux méthodes presque identiques log shader. La seule différence entre les deux méthodes originales a été le GLuint (entier non signé) la valeur passée dans les appels OpenGL ES deux fonctions.
Ce code me dérangeait encore, cependant. Regardez comment le shader similaires et la fonctionnalité journal de programme est. Il est identique sauf pour les deux appels de fonction et, j'ai réalisé, ces deux appels de fonction prendre exactement les mêmes paramètres. Il avait été un moment depuis que je les avais utilisés, de sorte qu'il a fallu que mon cerveau quelques secondes de trimer jusqu'à combien pointeur fonction C travaillé, mais une fois qu'il a fait, le chemin pour la refactorisation de ce code est devenu évident. Une seule méthode peut être utilisée pour tous les trois de ces cas, si j'ai pris des pointeurs de fonction de l'API OpenGL deux appels comme paramètres de méthode.
Or, cette seconde refactorisation est un que beaucoup de gens diraient probablement ne vaut pas la peine. C'est trop. Il ne passe pas une «analyse coûts-avantages». Le résultat ne va pas être que beaucoup plus courte, alors comment pouvez-vous justifier le temps? J'ai l'habitude de penser de la même façon avant de dépenser autant de temps à écrire du code pour la consommation publique, mais maintenant je suis devenu convaincu que la refactorisation comme celui-ci ne passent à l'analyse coût-bénéfice. Je n'étais pas mettre assez de poids sur la valeur du bon code lorsque vous faites cette analyse. Lorsque vous revenez à quelques semaines de code, mois ou années plus tard, vous ne serez pas se souvenir de tous les petits détails. Avec bien écrit le code, vous n'aurez pas à.
La vérité (dans mon expérience, au moins) est que le code refactorisé est assez facile à maintenir que cela vaut la peine de prendre le temps de le faire. Comme tout, plus vous le faites, mieux vous mettre au travail, et plus vite vous pouvez le faire. Finalement, il devient une seconde nature. Vous finissez par écrit un meilleur code à peu près la même quantité de temps que vous avez utilisé pour écrire du code bâclé, mais fonctionnel. De plus, chaque fois que vous visitez ce code dans l'avenir, vous avez un code un peu moins de chercher à travers. Chaque fois que vous avez à déboguer ce code, vous avez un code un peu moins pour passer. Chaque fois que vous corriger le code, vous avez moins de code à corriger.
Si j'avais une erreur dans la version originale de ces fonctions log, je aurait presque certainement la même erreur exacte en trois endroits. Quand j'ai découvert cette erreur, tu crois que je ne me souviens de le fixer dans les trois endroits? Peut-être, si j'ai écrit le code récemment suffit de se souvenir. Si quelqu'un d'autre fixaient mon code, pensez-vous qu'ils sachent qu'ils avaient de le fixer à trois endroits? Peut-être ... mais plus probablement ils ne seraient pas.
Voici la version définitive de la méthode que j'ai installés sur le livre ^ 1:
typedef void (*GLInfoFunction)(GLuint program, GLenum pname, GLint* params);
typedef void (*GLLogFunction) (GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog);
- (NSString *)logForOpenGLObject:(GLuint)object
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc
{
GLint logLength = 0, charsWritten = 0;
infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength);
logFunc(object, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding];
free(log);
return ret;
}
return @"No Log Data";
}
- (NSString *)vertexShaderLog
{
return [self logForOpenGLObject:vertShader
infoCallback:(GLInfoFunction)&glGetShaderiv
logFunc:(GLLogFunction)&glGetShaderInfoLog];
}
- (NSString *)fragmentShaderLog
{
return [self logForOpenGLObject:fragShader
infoCallback:(GLInfoFunction)&glGetShaderiv
logFunc:(GLLogFunction)&glGetShaderInfoLog];
}
- (NSString *)programLog
{
return [self logForOpenGLObject:program
infoCallback:(GLInfoFunction)&glGetProgramiv
logFunc:(GLLogFunction)&glGetProgramInfoLog];
}
Maintenant, si vous travaillez pour quelqu'un d'autre, surtout si vous travaillez dans une grande société, vous pouvez être mesurée sur une série de paramètres, y compris quelque chose comme "lignes de code écrites» (LOC). Bien que je comprenne la nécessité de la responsabilisation, la plupart des entreprises sont métriques (pour être franc) d'un abus de conneries de la statistique. La métrique absolue pire n'est jamais imaginé LOC. Si vous travaillez quelque part qui est encore dans le codage âges sombres et vous mesurer par LOC, courir comme un diable, ou au moins faire pression pour un changement de cette politique. Mesurer la performance basée sur le LOC encourage même de bons ingénieurs pour écrire bâclée, code horrible.
Dans mon exemple, j'ai passé une partie de mon temps à améliorer mon code par enlever 25 lignes de code. J'ai passé beaucoup de temps de le prendre à partir de 64 lignes de code à 39 lignes de code. En tant que développeur, je ne devrais pas être puni pour faire ça!
Dans des situations réelles, nous avons souvent nous convaincre qu'il n'y a pas de temps pour la refactorisation de code pendant que nous. Après tout, nous devons faire ce délai, et il ya always une date limite! Nous pouvons toujours revenir et factoriser le code plus tard quand nous avons plus de temps, non?
Seul vous ne serez pas. Vous ne reviendra pas jusqu'à ce que vous devez absolument, et par ce point, il serait probablement plus rapide de le réécrire. Si vous travaillez pour quelqu'un d'autre, ils sont peu susceptibles de vous laisser aller en arrière et passer du temps "fixer" quelque chose qui fonctionne parfaitement bien. C'est particulièrement vrai si vous avez un gestionnaire de non-techniques. En outre, vous serez probablement sur votre prochaine échéance déjà et la pression du temps se sentir à nouveau.
À long terme, le code de bonnes vaut le temps qu'il faut. Souvent, ça vaut le coup dans le court terme aussi bien.
Prenez le temps de remanier, et non après le fait qu'il fasse partie de votre processus de développement. Faites-le d'emblée, avant qu'il ne passe à l'essai, parce que vous ne reviendront pas. Relisez votre code avant de vous envoyer un build.
Ou, en d'autres termes, le code comme si chacun était capable de voir et de comprendre votre code.
Addendum
Il ya un autre point que je veux faire. Non seulement devez-vous le code comme s'il y avait beaucoup de yeux sur votre code, vous devriez réellement obtenir beaucoup de regard sur votre code si vous le pouvez. Vous ratez des choses même si vous faites la relecture et la refactorisation partie de votre processus de développement. Vous aurez même parfois manquer des trucs stupides. Affaire au point, j'ai reçu un e-mail peu de temps après la soumission de ce à partir Brent Simmons of NetNewsWire renommée. Brent très gentiment pris le temps de relire le code et fait remarquer un bogue et plusieurs améliorations qui pourraient être apportées.
Voici ses suggestions:
- Retour au début si logLength < 1, so that the main functionality doesn't have to be indented.
- Retour d'une chaîne autoreleased, par des conventions de codage d'Apple
- Retour nulle dans le cas d'absence de données du journal, plutôt que d'une chaîne qui devrait être comparés afin de déterminer s'il y avait ou non les données du journal.
- Changer le nom du char * et les variables NSString à quelque chose d'un peu plus significative. (Puisque la méthode est logForSomething, elle retourne un journal variable nommée.)
- (NSString *)logForOpenGLObject:(GLuint)object
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc
{
GLint logLength = 0, charsWritten = 0;
infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);
if (logLength < 1)
return nil;
char *logBytes = malloc(logLength);
logFunc(object, logLength, &charsWritten, logBytes);
NSString *log = [[[NSString alloc] initWithBytes:logBytes
length:logLength
encoding:NSUTF8StringEncoding] autorelease];
free(logBytes);
return log;
}Merci, Brent!
1 Cela peut, bien sûr, le changement si mon relecteur technique détecte un problème avec elle
Aucun commentaire:
Enregistrer un commentaire