Revisiones de códigos: fuentes comunes de violaciones extremas y cómo evitar argumentos sobre su solución

Se sacan los cuchillos. Las cuchillas están afiladas para el conflicto. Se desata una disputa entre los desarrolladores. Las pasiones de los codificadores se inflaman no por el software con errores, sino por el código conciso o detallado. Esas líneas son signos de un hack. Cualquier programador que no esté de acuerdo es un aficionado. Solo un neófito produciría métodos y bloques que violen tan claramente el buen gusto. Sin embargo, las diferentes preferencias, no las leyes de la naturaleza, son la fuente de este conflicto y el vitriolo. El odio entre los desarrolladores es, en este caso, el resultado de diferentes inclinaciones hacia la concisión comercial para fines diferentes. Estos objetivos, y la tendencia a alcanzarlos, son diferentes para cada desarrollador, lo que lleva a conflictos constantes en ciertas áreas. Uno de esos lugares es el código wordy o pithy. Para minimizar el combate, un equipo puede usar revisiones de código para resaltar los segmentos más atroces, y el grupo puede discutir sobre esas partes en lugar de discutir sobre cada línea y bloque de una base de código.

Es probable que ciertas construcciones o técnicas de código produzcan violaciones extremas y generen argumentos sobre la corrección de esas infracciones. Arreglar esos abusos conduce a discusiones intensas. Estos desacuerdos se pueden resolver, o al menos desescalar, para las características y técnicas lingüísticas particulares que se enumeran a continuación.

Operador condicional versus declaración If

Los elementos lingüísticos, el operador condicional y el enunciado if conducen a argumentos, con diferentes campos que argumentan que cada uno es la técnica superior para ciertas operaciones. Estas acciones se pueden implementar de innumerables maneras, cada técnica trae ventajas y desventajas.

Declaración If: La declaración if puede contribuir a un código atrozmente voluminoso, cuando la densidad de las condiciones es alta. Una alta densidad hace que un bloque o método parezca hinchado. Sin embargo, el código escrito con sentencias if también es altamente depurable, ya que un desarrollador puede recorrer cada línea.

if (label1IsRequired) {
 label1.Color = "rojo";
} más {
 label1.Color = "negro";
}
if (label2IsRequired) {
 label2.Color = "rojo";
} más {
 label2.Color = "negro";
}
if (label3IsRequired) {
 label3.Color = "rojo";
} más {
 label3.Color = "negro";
}

Operador condicional: El operador condicional puede conducir a algunas líneas claramente concisas, cuando se usa como reemplazo de varias declaraciones if. Los operadores condicionales integrados hacen que el código, cuando se lleva al extremo, sea muy difícil de leer, probar o depurar. Cualquier bloque o método pesado en operadores condicionales también es muy compacto, lo que reduce la cantidad de código que un desarrollador debe escanear.

healthIndicatorColor = (salud == "Bueno")? "Verde": (salud == "Feria")? "Amarillo": (salud == "pobre")? "Rojo": (salud == "soporte_vida")? "Naranja": "púrpura";

Resolución potencial: los operadores condicionales son beneficiosos cuando reemplazan una alta densidad de valores establecidos en función de las condiciones implementadas a través de sentencias if. Los operadores condicionales son destructivos cuando reemplazan incluso un par de decisiones incrustadas entre sí. Los imperativos que se ajustan de manera legible en una línea son un objetivo principal para los operadores condicionales, mientras que las condiciones que requieren varias líneas son el dominio de las declaraciones if. Cualquier uso atroz de sentencias if u operadores condicionales debe corregirse para implementar el uso apropiado de una de esas construcciones. (Nota: la modificación puede requerir una refactorización significativa).

if (salud == "Bueno") {
 healthIndicatorColor = "verde";
} else if (health == "Fair") {
 healthIndicatorColor = "amarillo";
} más if (salud == "pobre") {
 healthIndicatorColor = "rojo";
} else if (health == "life_support") {
 healthIndicatorColor = "naranja";
} más {
 healthIndicatorColor = "púrpura";
}
label1.Color = (label1IsRequired)? "negro rojo";
label2.Color = (label2IsRequired)? "negro rojo";
label3.Color = (label3IsRequired)? "negro rojo";

Declaraciones de devolución múltiple versus una declaración de devolución

Dos estilos particulares que conducen a argumentos son los retornos múltiples y los retornos únicos. Surgen desacuerdos sobre si los métodos deben tener una declaración de retorno o si se aceptan múltiples declaraciones de retorno. Cada enfoque tiene aspectos positivos y negativos.

Declaraciones de devolución múltiple: las declaraciones de devolución múltiple pueden contribuir al código que es difícil de entender, seguir y probar. Sin embargo, los métodos con múltiples retornos pueden ser más cortos que las funciones con un solo retorno.

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

Una declaración de devolución: una sola declaración de devolución puede conducir a métodos largos. Sin embargo, estos procedimientos tienen una salida de punto único, lo que simplifica las pruebas y la depuración.

SomeDataType someMethod () {
 SomeDataType retVal;
 // cientos o miles de código de líneas
 retorno retVal;
}

Resolución potencial: los retornos múltiples hacen que el código sea difícil de entender, seguir y probar cuando se usan de manera inconsistente. Las declaraciones de retorno individuales conducen a métodos largos, cuando son procesados ​​por largos tramos de código. Esos tramos extendidos pueden acortarse, o al menos hacerse legibles, utilizando varias declaraciones de devolución en lugar de una. Los retornos individuales son perfectamente aceptables cuando siguen breves extensiones de código. Cualquier uso indebido deslumbrante de una sola declaración de devolución o múltiples devoluciones debe corregirse para aplicar un caso de uso aceptado de uno de esos estilos. (Nota: la corrección puede requerir una refactorización significativa).

SomeDataType someMethod (param1, param2, param3) {
 if (param1 == null || param3 == null) {
 volver nulo;
 }
 
 SomeDataType retVal = null;
 if (param2! = null {
 retVal = param2;
 } else if (param1! = null) {
 retVal = param1;
 } ele if (param3! = null) {
 retVal = param3;
 }
 retorno retVal;
}
SomeDataType someMethod (param1, param2) {
 SomeDataType retVal = null;
 for (int i = 0; i  if (param1 [i] .equals (param2)) {
 retVal = param1 [i];
 rotura;
 }
 }
 retorno retVal;
} Romper y continuar el uso en bucles

Las construcciones de ruptura y continuación son objeto de intensos debates. En un lado del argumento, los desarrolladores sostienen que romper y continuar puede simplificar el flujo de control. Otros programadores sostienen que estas características complican la lógica de un programa. Romper y continuar definitivamente se puede usar para simplificar o complicar el código. Estas líneas se pueden ver.

Interrupción y uso continuo: los elementos pueden simplificar el código, pero también pueden complicarlos innecesariamente.

SomeDataType someMethod (param1, param2) {
 SomeDataType retVal = null;
 for (int i = 0; i  if (param1 [i] == null) {
 Hacer continuación;
 }
 if (param1 [i] .equals (param2)) {
 retVal = param1 [i];
 rotura;
 }
 }
 retorno retVal;
}
SomeDataType someMethod (data, param1) {
 SomeDataType retVal = null;
 hacer algo:
 for (int i = 0; i  if (i> = data.length) {
 rotura;
 }
 if (datos [i] .equals (param1)) {
 retVal = datos [i];
 } más {
 Hacer continuación;
 }
 }
if (retVal == null) {
 datos - refreshData ();
 ir a dosomething;
 }
retorno retVal;
}

Resolución potencial: la mayoría de los desarrolladores argumentan que el código debe usar mecanismos simples para el flujo de control. Qué mecanismos específicos son simples es la fuente del debate. Este argumento se vuelve mucho menos acalorado cuando cada instrumento se usa de manera ampliamente aceptada. Existen enfoques aceptados para el descanso y continuar. Apéguese a esas convenciones para evitar desacuerdos y simplificar el flujo de control. Cualquier medio de control que viole flagrantemente esos estándares debe corregirse sin debate.

SomeDataType someMethod (data, param1) {
 SomeDataType retVal = null;
 for (int i = 0; i  if (datos [i] == nulo) {
 Hacer continuación; // omite el resto del bucle
 }
 if (datos [i] .equals (param2)) {
 retVal = datos [i];
 rotura; // no repitas más b / c que he terminado
 }
 }
 retorno retVal;
}

Excepciones defensivas

Las excepciones son un medio para indicar un problema o para evitar un problema futuro. Qué dolores de cabeza deberían indicarse o evitarse por qué partes del código es un tema de feroz debate. En un extremo del desacuerdo, los codificadores argumentan que las excepciones defensivas generalizadas evitan errores y los hacen fáciles de ubicar. Sin embargo, esa larga lista de defensa puede hacer que el código se hinche y sea difícil de entender, como han argumentado algunos programadores. Los desarrolladores de ambos lados del debate tienen un punto. Las excepciones defensivas tienen tanto beneficios como perjuicios.

Beneficios y perjuicios de las excepciones defensivas: la protección contra errores y otros problemas puede protegerse, con inconvenientes mínimos, utilizando excepciones defensivas. Esos defectos se magnifican cuando la técnica es indiscriminada.

anular someMethod (param1, param2) {
 if (param1 == null || param2 == null) {
 lanzar una nueva ArgumentNullException ("Falta uno o más parámetros");
 }
 // hacer cosas de método
}
anular someMethod (param1, param2) {
 // docenas de líneas de controles defensivos ...
 // hacer el resto del método
}

Resolución potencial: las deficiencias de las excepciones defensivas son más pequeñas cuando se implementan en usos aceptados. Cualquier implementación de la técnica que se desvíe de esas convenciones debe corregirse, a menos que se proporcione una razón convincente.

public void someMethod (param1, param2) {
 // verifica cada parámetro en un método público
 if (param1 == null || param2 == null) {
 lanzar una nueva ArgumentNullException ("Falta uno o más parámetros");
 }
 
 // evitar problemas causados ​​por datos no válidos
 if (! isValid (param1) ||! isValid (param2)) {
 lanzar una nueva InvalidParameterException ("Uno o más parámetros no son válidos");
 }
 
 // hacer algo con parámetros
}

Envolver

Estas construcciones y técnicas de código son utilizadas por desarrolladores buenos y malos. Los programadores son personas. Los seres humanos tienen tendencias. Estas inclinaciones se manifiestan en código. Ocasionalmente, los impulsos de un desarrollador lo llevan a escribir código que otros codificadores critican correctamente. El desarrollador que está en la picota no es necesariamente un mal programador. El codificador que lo critica no es necesariamente un buen desarrollador. Es probable que ambas personas hayan sido extraviadas, en un punto, por sus preferencias. Estos deseos no deberían llevar al desarrollo a degenerar en una corriente interminable de insultos lanzados el uno al otro. Por el contrario, los programadores deben revisar el código de cada uno, limitar sus batallas a sus peores secciones y acordar resolver ciertos argumentos a través de las reglas establecidas anteriormente.