Decidí escribir un compendio del libro, que todos conocen, y el autor lo llama la "Escuela de Maestros de Código Puro". La mirada de Martin parece estar diciendo:
“Veo a través de ti. ¿No sigues los principios del código limpio de nuevo?

Capítulo 1. Código limpio
¿Cuál es este código de versión Martin más limpio en pocas palabras? Este es un código sin duplicación, con un número mínimo de entidades, fácil de leer, simple. Como lema, uno podría elegir: "¡La claridad está por encima de todo!".
Capítulo 2. Nombres significativos
Los nombres deben transmitir las intenciones del programador.
El nombre de una variable, función o clase debe indicar por qué existe esta variable, qué hace y cómo se usa. Si el nombre requiere comentarios adicionales, no transmite las intenciones del programador. Es mejor escribir qué se mide exactamente y en qué unidades.
Un ejemplo de un buen nombre de variable: daysSinceCreation;
Propósito: eliminar la no evidencia.
Evitar la desinformación
No use palabras con significados ocultos que no sean los previstos. Cuidado con las sutiles diferencias en los nombres. Por ejemplo, XYZControllerForEfficientHandlingOfStrings y XYZControllerForEfficientStorageOfStrings.
Ejemplos verdaderamente aterradores de nombres mal informados se encuentran usando la "L" minúscula y la "O" mayúscula en los nombres de variables, especialmente las combinaciones. Naturalmente, surgen problemas debido al hecho de que estas letras casi no difieren de las constantes "1" y "0", respectivamente.
Usa diferencias significativas
Si los nombres son diferentes, deberían indicar conceptos diferentes.
Las "series de números" de la forma (a1, a2, ... aN) son lo opuesto a los nombres conscientes. No llevan información y no dan una idea de las intenciones del autor.
Las palabras no informativas son redundantes. La palabra variable nunca debe aparecer en los nombres de las variables. La palabra tabla nunca debe aparecer en los nombres de las tablas. ¿Por qué es NameString mejor que Name? ¿Puede un nombre ser, digamos, un número real?
Usa nombres ortográficos: generationTimestamp es mucho mejor que genymdhms.
Elija nombres de búsqueda
Los nombres de una letra solo se pueden usar para variables locales en métodos cortos.
Evitar esquemas de codificación de nombres
Por lo general, los nombres codificados están mal pronunciados y es fácil hacer un error tipográfico en ellos.
Interfaces e implementaciones
Yo (el autor del libro) prefiero dejar nombres de interfaz sin prefijos. El prefijo I, que es tan común en el código anterior, distrae en el mejor de los casos y, en el peor, transmite información innecesaria. No voy a decirles a mis usuarios que están tratando con una interfaz.
Nombres de clase
Los nombres de clase y objeto deben ser sustantivos y sus combinaciones: Cliente, WikiPage, Cuenta y AddressParser. Evite usar palabras como Administrador, Procesador, Datos o Información en los nombres de clase. El nombre de la clase no debe ser un verbo.
Nombres de métodos
Los nombres de los métodos son verbos o frases verbales: postPayment, deletePage, save, etc. Los métodos de lectura / escritura y los predicados se forman a partir del valor y el prefijo get, set y está de acuerdo con el estándar javabean.
Abstenerse de juegos de palabras
La tarea del autor es hacer que su código sea lo más claro posible. El código debe percibirse de un vistazo, sin requerir un estudio cuidadoso. Concéntrese en el modelo de literatura popular, en el que el propio autor debe expresar libremente sus pensamientos.
Añadir contexto significativo.
El contexto se puede agregar usando prefijos: addrFirstName, addrLastName, addrState, etc. Al menos el lector de código comprenderá que las variables son parte de una estructura más grande. Por supuesto, sería más correcto crear una clase llamada Address, de modo que incluso el compilador sepa que las variables son parte de algo más.
Variables con contexto poco claro:
private void printGuessStatistics(char candidate, int count) { String number; String verb; String pluralModifier; if (count == 0) { number = "no"; verb = "are"; pluralModifier = "s"; } else if (count == 1) { number = ~_~quot quot~_~; verb = "is"; pluralModifier = ""; } else { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } String guessMessage = String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier ); print(guessMessage); }
La función es un poco larga y las variables se utilizan en todo momento. Para dividir la función en fragmentos semánticos más pequeños, debe crear la clase GuessStatisticsMessage y hacer que tres variables sean los campos de esta clase. De esta manera, proporcionaremos un contexto obvio para las tres variables; ahora es absolutamente obvio que estas variables son parte de GuessStatisticsMessage.
Variables con contexto:
public class GuessStatisticsMessage { private String number; private String verb; private String pluralModifier; public String make(char candidate, int count) { createPluralDependentMessageParts(count); return String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier ); } private void createPluralDependentMessageParts(int count) { if (count == 0) { thereAreNoLetters(); } else if (count == 1) { thereIsOneLetter(); } else { thereAreManyLetters(count); } } private void thereAreManyLetters(int count) { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } private void thereIsOneLetter() { number = ~_~quot quot~_~; verb = "is"; pluralModifier = ""; } private void thereAreNoLetters() { number = "no"; verb = "are"; pluralModifier = "s"; } }
No agregue contexto redundante
Los nombres cortos son generalmente mejores que los nombres largos, si solo su significado es claro para el lector de código. No incluya más contexto del necesario en el nombre.
Capítulo 3. Funciones
Compacto!
Primera regla: las funciones deben ser compactas.
La segunda regla: las funciones deberían ser aún más compactas.
Mi experiencia práctica me ha enseñado (a costa de muchas pruebas y errores) que las funciones deberían ser muy pequeñas. Es deseable que la longitud de la función no supere las 20 líneas.
Regla de una operación
Una función debe realizar solo una operación. Ella debe realizarlo bien. Y ella no debería hacer nada más. Si una función realiza solo aquellas acciones que están en el mismo nivel bajo el nombre declarado de la función, entonces esta función realiza una operación.
Secciones de funciones
Una función que realiza una sola operación no puede dividirse significativamente en secciones.
Un nivel de abstracción por función.
Para asegurarse de que la función realiza "una sola operación", es necesario verificar que todos los comandos de la función estén en el mismo nivel de abstracción.
La combinación de niveles de abstracción dentro de una función siempre crea confusión.
Lectura de código de arriba a abajo: regla de degradación
El código debería leerse como una historia, de arriba abajo.
Cada función debe ir seguida de funciones del siguiente nivel de abstracción. Esto le permite leer el código, bajando secuencialmente los niveles de abstracción mientras lee la lista de funciones. Yo llamo a este enfoque la "regla de rebaja".
Cambiar comandos
Escribir un comando de interruptor compacto es bastante difícil. Incluso un comando de cambio con solo dos condiciones ocupa más espacio del que debería ocupar un solo bloque o función en mi opinión. También es difícil crear un comando de conmutación que haga una cosa: por naturaleza, los comandos de conmutación siempre realizan N operaciones. Desafortunadamente, los comandos de cambio no siempre se eliminan, pero al menos podemos asegurarnos de que estos comandos estén ocultos en una clase de bajo nivel y no estén duplicados en el código. Y, por supuesto, el polimorfismo puede ayudarnos con esto.
El ejemplo muestra solo una operación, dependiendo del tipo de empleado.
public Money calculatePay(Employee e) throws InvalidEmployeeType { switch (e.type) { case COMMISSIONED: return calculateCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.type); } }
Esta característica tiene varias desventajas. En primer lugar, es genial, y con la incorporación de nuevos tipos de trabajadores crecerá. En segundo lugar, lo más obvio es que realiza más de una operación. En tercer lugar, viola el principio de responsabilidad única, ya que tiene varias razones posibles para el cambio.
Cuarto, viola el Principio de Cerrado Abierto, porque el código de función debe cambiar cada vez que se agregan nuevos tipos.
Pero quizás el inconveniente más grave es que el programa puede contener un número ilimitado de otras funciones con una estructura similar, por ejemplo:
isPayday (Empleado e, Fecha fecha)
o
deliveryPay (empleado e, pago de dinero)
Y así sucesivamente.
Todas estas funciones tendrán la misma estructura defectuosa. La solución a este problema es enterrar el comando de cambio en la base de la fábrica abstracta y no mostrarlo a nadie. La fábrica utiliza el comando switch para crear las instancias correspondientes de los descendientes de Employee, y las llamadas a las funciones CalculatePay, isPayDay, deliverPay, etc., pasan la transferencia polimórfica a través de la interfaz de Employee.
public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay(Money pay); } ----------------- public interface EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; } ----------------- public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType { switch (r.type) { case COMMISSIONED: return new CommissionedEmployee(r) ; case HOURLY: return new HourlyEmployee(r); case SALARIED: return new SalariedEmploye(r); default: throw new InvalidEmployeeType(r.type); } } }
Mi regla general con respecto a los comandos de cambio es que estos comandos son válidos si ocurren una vez en el programa, se usan para crear objetos polimórficos y se esconden detrás de las relaciones de herencia para permanecer invisibles para el resto del sistema. Por supuesto, no hay reglas sin excepciones, y en algunas situaciones es necesario violar una o más condiciones de esta regla.
Usa nombres significativos
La mitad del esfuerzo para implementar este principio se reduce a elegir buenos nombres para funciones compactas que realizan una sola operación. Cuanto más pequeña y especializada sea la función, más fácil será elegir un nombre significativo para ella.
No tenga miedo de usar nombres largos, un nombre largo y significativo es mejor que uno corto y oscuro. Elija un esquema que facilite la lectura de las palabras en el nombre de la función, y luego cree un nombre a partir de estas palabras que describa el propósito de la función.
Argumentos de funciones
En el caso ideal, el número de argumentos de función es cero. Las siguientes son funciones con un argumento (unario) y con dos argumentos (binario). Las funciones con tres argumentos (ternario) deben evitarse siempre que sea posible.
Los argumentos de salida confunden la situación incluso más rápido que la entrada. Como regla, nadie espera que una función devuelva información en argumentos. Si no puede administrar sin argumentos, intente al menos limitarse a un argumento de entrada.
Las conversiones que usan un argumento de salida en lugar de un valor de retorno confunden al lector. Si la función convierte su argumento de entrada, entonces el resultado
debe pasarse en el valor de retorno.
Argumentos de banderas
Los argumentos del argumento son feos. Pasar el significado lógico de una función es un hábito verdaderamente terrible. Inmediatamente complica la firma del método, proclamando en voz alta que la función realiza más de una operación. Si la bandera es verdadera, se realiza una operación y, si es falsa, otra.
Funciones binarias
Una función con dos argumentos es más difícil de entender que una función unaria. Por supuesto, en algunas situaciones, una forma de dos argumentos es apropiada. Por ejemplo, llamando al Punto p = nuevo Punto (0,0); absolutamente razonable Sin embargo, dos argumentos en nuestro caso son componentes ordenados del mismo valor.
Objetos como argumentos
Si una función recibe más de dos o tres argumentos, es muy probable que algunos de estos argumentos se empaqueten en una clase separada. Considere las siguientes dos declaraciones:
Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);
Si las variables se transfieren juntas como un todo (como las variables x e y en este ejemplo), entonces, muy probablemente, juntas forman un concepto que merece su propio nombre.
Verbos y palabras clave
Elegir un buen nombre para una función puede explicar en gran medida el significado de la función, así como el orden y el significado de sus argumentos. En funciones unarias, la función misma y su argumento deben formar un par verbo / sustantivo natural. Por ejemplo, una llamada del formulario write (name) parece muy informativa.
El lector entiende que no importa cuál sea el "nombre", está en algún lugar "escrito". Aún mejor es el registro writeField (nombre), que informa que el "nombre" se escribe en el "campo" de alguna estructura.
La última entrada es un ejemplo del uso de palabras clave en el nombre de una función. De esta forma, los nombres de argumentos están codificados en el nombre de la función. Por ejemplo, afirmarEquals se puede escribir como afirmarExpendidoEquualesActual (esperado, real). Esto resuelve en gran medida el problema de recordar el orden de los argumentos.
Separación de comandos y solicitudes.
Una función debe hacer algo o responder alguna pregunta, pero no simultáneamente. La función cambia el estado del objeto o devuelve información sobre este objeto. La combinación de dos operaciones a menudo crea confusión.
Aislar bloques try / catch
Los bloques try / catch se ven bastante feos. Confunden la estructura del código y mezclan el manejo de errores con el procesamiento normal. Por esta razón, se recomienda que los cuerpos de los bloques try y catch se separen en funciones separadas.
Manejo de errores como una operación
Las funciones deben realizar una operación. El manejo de errores es una operación. Esto significa que la función que procesa errores no debe hacer nada más. De ello se deduce que si la palabra clave try está presente en la función, entonces debe ser la primera palabra en la función, y después de los bloques catch / finally no debería haber nada más.
Esto concluye el Capítulo 3.