Examens de code: sources communes de violations extrêmes et comment éviter les arguments pour les résoudre

Les couteaux sont tirés. Les lames sont affûtées pour le conflit. Un différend fait rage entre les développeurs. Les passions des codeurs ne sont pas enflammées par un logiciel mais par un code extrêmement concis ou trop détaillé. Ces lignes sont des signes d'un hack. Tout programmeur qui n'est pas d'accord est un amateur. Seul un néophyte produirait des méthodes et des blocs qui violent si clairement le bon goût. Pourtant, ce sont ces conflits et le vitriol qui sont à l’origine des préférences différentes et non des lois de la nature. La haine entre les développeurs est, dans ce cas, le résultat de différentes inclinaisons vers la succincte commerciale à des fins différentes. Ces objectifs, et leur tendance, sont différents pour chaque développeur, ce qui entraîne des conflits constants dans certains domaines. Un tel endroit est le code verbeux ou pithy. Pour minimiser les combats, une équipe peut utiliser les révisions de code pour mettre en évidence les segments les plus flagrants, et le groupe peut discuter de ces parties au lieu de se disputer à propos de chaque ligne et bloc d'une base de code.

Certaines constructions de code ou techniques sont susceptibles de produire des violations extrêmes et de faire valoir des arguments pour corriger ces infractions. La réparation de ces abus conduit à des débats intenses. Ces désaccords peuvent être résolus, ou au moins désescaladés, pour les caractéristiques et techniques linguistiques énumérées ci-dessous.

Déclaration conditionnelle d'opérateur contre si

Les éléments linguistiques, l'opérateur conditionnel et l'instruction if, conduisent à des arguments, différents camps affirmant que chacun d'eux est la technique supérieure pour certaines opérations. Ces actions peuvent être mises en œuvre de multiples façons, chaque technique apportant des avantages et des inconvénients.

Instruction If: L'instruction if peut contribuer à un code extrêmement volumineux lorsque la densité des conditions est élevée. Une densité élevée donne l'impression qu'un bloc ou une méthode est bouffi. Cependant, le code écrit avec des instructions if est également hautement débogable, puisqu'un développeur peut parcourir chaque ligne.

if (label1IsRequired) {
 label1.Color = “rouge”;
} autre {
 label1.Color = "noir";
}
if (label2IsRequired) {
 label2.Color = “rouge”;
} autre {
 label2.Color = "noir";
}
if (label3IsRequired) {
 label3.Color = “rouge”;
} autre {
 label3.Color = "noir";
}

Opérateur conditionnel: L'opérateur conditionnel peut conduire à certaines lignes laconiques, lorsqu'il est utilisé en remplacement de plusieurs instructions if. Les opérateurs conditionnels intégrés rendent le code extrêmement difficile à lire, à tester ou à mettre au point. Tout bloc ou toute méthode lourde en opérateurs conditionnels est également très compact, ce qui réduit la quantité de code qu'un développeur doit analyser.

healthIndicatorColor = (health == «Bon»)? «Vert»: (santé == «passable»)? “Jaune”: (santé == “pauvre”)? “Rouge”: (santé == “life_support”)? "Orange": "violet";

Résolution potentielle: les opérateurs conditionnels sont avantageux lorsqu'ils remplacent une forte densité de valeurs définie en fonction de conditions mises en œuvre via des instructions if. Les opérateurs conditionnels sont destructeurs, lorsqu'ils remplacent même quelques décisions imbriquées les unes dans les autres. Les impératifs qui correspondent parfaitement à une ligne constituent une cible de choix pour les opérateurs conditionnels, tandis que les conditions nécessitant plusieurs lignes relèvent du domaine des instructions if. Toute utilisation flagrante d'instructions if ou d'opérateurs conditionnels doit être corrigée pour déployer l'utilisation appropriée de l'une de ces constructions. (Remarque: la modification peut nécessiter une refactorisation importante.)

si (santé == «bien») {
 healthIndicatorColor = “green”;
} sinon si (santé == «passable») {
 healthIndicatorColor = “yellow”;
} sinon si (santé == “pauvre”) {
 healthIndicatorColor = “rouge”;
} else if (health == “life_support”) {
 healthIndicatorColor = "orange";
} autre {
 healthIndicatorColor = “violet”;
}
label1.Color = (label1IsRequired)? "rouge noir";
label2.Color = (label2IsRequired)? "rouge noir";
label3.Color = (label3IsRequired)? "rouge noir";

Relevés multiples contre un relevé

Deux styles particuliers conduisant à des arguments sont les retours multiples et les retours uniques. Des désaccords émergent sur le point de savoir si les méthodes doivent avoir une seule instruction return ou si plusieurs instructions return sont acceptables. Chaque approche a des aspects positifs et négatifs.

Instructions de retour multiples: les instructions contenant plusieurs déclarations peuvent contribuer à un code difficile à comprendre, à suivre et à tester. Cependant, les méthodes avec plusieurs retours peuvent être plus courtes que les fonctions avec un seul retour.

SomeDataType someMethod (param1, param2, param3) {
 SomeDataType retVal;
 if (param1 == null) {
 retVal = null
 }
 if (retVal == null) {
 retour retour;
 }
 
 si (param2! = null) {
 retVal = param2;
 }
 if (retVal.Equals (param2)) {
 retour retour;
 }
 
 retVal = param3;
 retour retour;
}

Une déclaration de retour: Une seule déclaration de retour peut conduire à de longues méthodes. Pourtant, ces procédures ont un seul point de sortie, simplifiant les tests et le débogage.

SomeDataType someMethod () {
 SomeDataType retVal;
 // des centaines ou des milliers de lignes de code
 retour retour;
}

Solution possible: Les retours multiples rendent le code difficile à comprendre, à suivre et à tester, lorsqu'ils sont utilisés de manière incohérente. Les instructions return uniques conduisent à de longues méthodes, lorsqu'elles sont traitées par de longues périodes de code. Ces étendues peuvent être raccourcies, ou au moins rendues lisibles, en utilisant plusieurs instructions de retour au lieu d'une. Les retours simples sont parfaitement acceptables lorsqu'ils suivent de courtes parcelles de code. Toute utilisation abusive flagrante d'une déclaration unique ou de déclarations multiples doit être corrigée pour appliquer un cas d'utilisation accepté de l'un de ces styles. (Remarque: la correction peut nécessiter une refactorisation importante.)

SomeDataType someMethod (param1, param2, param3) {
 if (param1 == null || param3 == null) {
 return null;
 }
 
 SomeDataType retVal = null;
 si (param2! = null {
 retVal = param2;
 } sinon si (param1! = null) {
 retVal = param1;
 } ele if (param3! = null) {
 retVal = param3;
 }
 retour retour;
}
SomeDataType someMethod (param1, param2) {
 SomeDataType retVal = null;
 pour (int i = 0; i  if (param1 [i] .equals (param2)) {
 retVal = param1 [i];
 Pause;
 }
 }
 retour retour;
} Utilisation interrompue et continue dans les boucles

Les constructions break et continue font l’objet de débats intenses. D'un côté de l'argument, les développeurs soutiennent que l'interruption et la poursuite peuvent simplifier le flux de contrôle. D’autres programmeurs affirment que ces fonctionnalités compliquent la logique d’un programme. Break and continue peut certainement être utilisé pour simplifier ou compliquer le code. Ces lignes peuvent être repérées.

Utilisation de Break and Continue: les éléments peuvent simplifier le code, mais ils peuvent également les compliquer inutilement.

SomeDataType someMethod (param1, param2) {
 SomeDataType retVal = null;
 pour (int i = 0; i  if (param1 [i] == null) {
 Continuez;
 }
 if (param1 [i] .equals (param2)) {
 retVal = param1 [i];
 Pause;
 }
 }
 retour retour;
}
SomeDataType someMethod (data, param1) {
 SomeDataType retVal = null;
 faire quelque chose:
 pour (int i = 0; i  si (i> = data.length) {
 Pause;
 }
 if (data [i] .equals (param1)) {
 retVal = data [i];
 } autre {
 Continuez;
 }
 }
if (retVal == null) {
 data - refreshData ();
 aller à quelque chose;
 }
retour retour;
}

Résolution potentielle: la plupart des développeurs soutiennent que le code devrait utiliser des mécanismes simples pour le flux de contrôle. Quels mécanismes spécifiques sont simples est la source du débat. Cet argument devient beaucoup moins attrayant lorsque chaque instrument est utilisé de manière largement acceptée. Les approches acceptées existent pour la pause et continuent. S'en tenir à ces conventions pour éviter les désaccords et simplifier le flux de contrôle. Tout moyen de contrôle qui enfreint de manière flagrante ces normes doit être corrigé sans débat.

SomeDataType someMethod (data, param1) {
 SomeDataType retVal = null;
 pour (int i = 0; i  if (data [i] == null) {
 Continuez; // saute le reste de la boucle
 }
 if (data [i] .equals (param2)) {
 retVal = data [i];
 Pause; // ne boucle plus b / c j'ai fini
 }
 }
 retour retour;
}

Exceptions défensives

Les exceptions sont un moyen d'indiquer un problème ou de parer à un problème futur. Quels maux de tête devraient être indiqués ou évités par quelles parties du code sont un sujet de débat acharné. À une extrémité du désaccord, les codeurs affirment que les exceptions défensives omniprésentes évitent les erreurs et facilitent leur localisation. Cependant, cette longue liste de moyens de défense peut rendre le code volumineux et difficile à comprendre, comme l'ont expliqué certains programmeurs. Les développeurs des deux côtés du débat ont un point. Les exceptions défensives présentent à la fois des avantages et des inconvénients.

Avantages et inconvénients des exceptions défensives: La protection contre les erreurs et autres problèmes peut être protégée, avec des inconvénients minimes, en utilisant des exceptions défensives. Ces défauts sont amplifiés lorsque la technique est utilisée sans discernement.

void someMethod (param1, param2) {
 if (param1 == null || param2 == null) {
 jette new ArgumentNullException ("Un ou plusieurs paramètres sont manquants");
 }
 // faire des trucs de méthode
}
void someMethod (param1, param2) {
 // des dizaines de lignes de contrôles défensifs… ..
 // reste de la méthode
}

Résolution potentielle: les lacunes des exceptions défensives sont les plus petites, lorsqu'elles sont déployées dans des usages acceptés. Tout déploiement de la technique qui déroge à ces conventions doit être corrigé, sauf si une raison impérieuse est fournie.

public void someMethod (param1, param2) {
 // vérifie chaque paramètre dans une méthode publique
 if (param1 == null || param2 == null) {
 jette new ArgumentNullException ("Un ou plusieurs paramètres sont manquants");
 }
 
 // élimine les problèmes causés par des données non valides
 if (! isValid (param1) ||! isValid (param2)) {
 jette new InvalidParameterException («Un ou plusieurs paramètres non valides»);
 }
 
 // faire quelque chose avec des paramètres
}

Emballer

Ces constructions et techniques de code sont utilisées à la fois par les bons et les mauvais développeurs. Les programmeurs sont des gens. Les êtres humains ont des tendances. Ces inclinations se manifestent dans le code. De temps en temps, les impulsions d’un développeur le poussent à écrire du code que d’autres codeurs critiquent à juste titre. Le développeur en cours de traitement n'est pas nécessairement un mauvais programmeur. Le codeur qui le critique n'est pas nécessairement un bon développeur. Les deux personnes ont probablement été égarées, à un moment donné, par leurs préférences. Ces désirs ne doivent pas conduire le développement à dégénérer en un flot incessant d'insultes lancées les unes sur les autres. Les programmeurs devraient plutôt examiner le code de chacun, limiter leurs batailles aux pires parties et accepter de régler certains arguments selon les règles énoncées ci-dessus.