JavaRush /Blog Java /Random-ES /Reglas para escribir código: desde crear un sistema hasta...

Reglas para escribir código: desde crear un sistema hasta trabajar con objetos

Publicado en el grupo Random-ES
Buenas tardes a todos: hoy me gustaría hablaros sobre cómo escribir código correctamente. Cuando comencé a programar, no estaba escrito claramente en ninguna parte que se pudiera escribir así, y si escribes así, te encontraré y…. Como resultado, tenía muchas preguntas en mi cabeza: cómo escribir correctamente, qué principios se deben seguir en esta o aquella sección del programa, etc. Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 1Bueno, no todo el mundo quiere sumergirse inmediatamente en libros como Clean Code, ya que en ellos se escribe mucho, pero al principio poco queda claro. Y cuando termine de leer, podrá desalentar todo deseo de codificar. Entonces, con base en lo anterior, hoy quiero brindarles una pequeña guía (un conjunto de pequeñas recomendaciones) para escribir código de nivel superior. En este artículo repasaremos las reglas y conceptos básicos relacionados con la creación de un sistema y el trabajo con interfaces, clases y objetos. Leer este material no le llevará mucho tiempo y, espero, no le permitirá aburrirse. Iré de arriba a abajo, es decir, de la estructura general de la aplicación a detalles más específicos. Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 2

Sistema

Las características generales deseables del sistema son:
  • Complejidad mínima : deben evitarse proyectos demasiado complicados. Lo principal es la sencillez y la claridad (mejor = simple);
  • facilidad de mantenimiento : al crear una aplicación, debe recordar que deberá ser compatible (incluso si no es usted), por lo que el código debe ser claro y obvio;
  • el acoplamiento débil es el número mínimo de conexiones entre diferentes partes del programa (uso máximo de los principios de programación orientada a objetos);
  • reutilización : diseñar un sistema con la capacidad de reutilizar sus fragmentos en otras aplicaciones;
  • portabilidad : el sistema debe adaptarse fácilmente a otro entorno;
  • estilo único: diseñar un sistema en un estilo único en sus diferentes fragmentos;
  • extensibilidad (escalabilidad) : mejorar el sistema sin alterar su estructura básica (si agrega o cambia un fragmento, esto no debería afectar al resto).
Es prácticamente imposible crear una aplicación que no requiera modificaciones sin agregar funcionalidad. Constantemente necesitaremos introducir nuevos elementos para que nuestra creación pueda mantenerse al día. Y aquí es donde entra en juego la escalabilidad . La escalabilidad es esencialmente expandir la aplicación, agregar nuevas funcionalidades, trabajar con más recursos (o, en otras palabras, con más carga). Es decir, debemos cumplir con algunas reglas, como reducir el acoplamiento del sistema aumentando la modularidad, para que sea más fácil agregar nueva lógica.

Etapas de diseño del sistema.

  1. Sistema de software : diseño de una aplicación en forma general.
  2. Separación en subsistemas/paquetes : definir partes lógicamente separables y definir las reglas de interacción entre ellas.
  3. Dividir subsistemas en clases : dividir partes del sistema en clases e interfaces específicas, así como definir la interacción entre ellas.
  4. Dividir clases en métodos es una definición completa de los métodos necesarios para una clase, en función de la tarea de esta clase. Diseño de métodos: definición detallada de la funcionalidad de los métodos individuales.
Normalmente, los desarrolladores ordinarios son responsables del diseño y el arquitecto de la aplicación es responsable de los elementos descritos anteriormente.

Principios y conceptos principales del diseño de sistemas.

Modismo de inicialización diferida Una aplicación no pierde tiempo creando un objeto hasta que se utiliza, lo que acelera el proceso de inicialización y reduce la carga del recolector de basura. Pero no se debe ir demasiado lejos, ya que esto puede conducir a una violación de la modularidad. Podría valer la pena trasladar todos los pasos del diseño a una parte específica, por ejemplo, principal, o a una clase que funcione como una fábrica . Uno de los aspectos de un buen código es la ausencia de código repetitivo que se repita con frecuencia. Como regla general, dicho código se coloca en una clase separada para poder llamarlo en el momento adecuado. AOP Por separado, me gustaría mencionar la programación orientada a aspectos . Se trata de programación mediante la introducción de lógica de un extremo a otro, es decir, el código repetido se coloca en clases (aspectos) y se llama cuando se alcanzan ciertas condiciones. Por ejemplo, al acceder a un método con un determinado nombre o al acceder a una variable de un determinado tipo. A veces los aspectos pueden resultar confusos, ya que no queda claro de inmediato desde dónde se llama el código, pero aún así, esta es una funcionalidad muy útil. En particular, al almacenar en caché o iniciar sesión: agregamos esta funcionalidad sin agregar lógica adicional a las clases regulares. Puede leer más sobre OAP aquí . 4 reglas para diseñar arquitectura simple según Kent Beck
  1. Expresividad : la necesidad de un propósito de la clase claramente expresado, se logra mediante la denominación correcta, el tamaño pequeño y el cumplimiento del principio de responsabilidad única (lo veremos con más detalle a continuación).
  2. Un mínimo de clases y métodos : en su deseo de dividir las clases en lo más pequeñas y unidireccionales posible, puede ir demasiado lejos (antipatrón - shotgunning). Este principio exige mantener el sistema compacto y no ir demasiado lejos, creando una clase para cada estornudo.
  3. Falta de duplicación : el código adicional que confunde es un signo de un diseño deficiente del sistema y se traslada a un lugar separado.
  4. Ejecución de todas las pruebas : un sistema que ha pasado todas las pruebas está controlado, ya que cualquier cambio puede provocar un fallo de las pruebas, lo que puede mostrarnos que un cambio en la lógica interna del método también provocó un cambio en el comportamiento esperado. .
SOLID Al diseñar un sistema, vale la pena tener en cuenta los principios bien conocidos de SOLID: S - responsabilidad única - el principio de responsabilidad única; O - abierto-cerrado - principio de apertura/cercanía; L - sustitución de Liskov - principio de sustitución de Barbara Liskov; I - segregación de interfaces - el principio de separación de interfaces; D - inversión de dependencia - principio de inversión de dependencia; No nos detendremos específicamente en cada principio (esto está un poco más allá del alcance de este artículo, pero puedes encontrar más información aquí) .

Interfaz

Quizás una de las etapas más importantes en la creación de una clase adecuada es crear una interfaz adecuada que represente una buena abstracción que oculte los detalles de implementación de la clase y, al mismo tiempo, represente un grupo de métodos que sean claramente consistentes entre sí. . Echemos un vistazo más de cerca a uno de los principios SOLID: la segregación de interfaces : los clientes (clases) no deben implementar métodos innecesarios que no utilizarán. Es decir, si estamos hablando de construir interfaces con un número mínimo de métodos destinados a realizar la única tarea de esta interfaz (para mí, es muy similar a responsabilidad única ), es mejor crear un par de más pequeños. unos en lugar de una interfaz inflada. Afortunadamente, una clase puede implementar más de una interfaz, como es el caso de la herencia. También es necesario recordar la denominación correcta de las interfaces: el nombre debe reflejar su tarea con la mayor precisión posible. Y, por supuesto, cuanto más corto sea, menos confusión provocará. Es en el nivel de la interfaz donde generalmente se escriben los comentarios para la documentación , que, a su vez, nos ayudan a describir en detalle qué debe hacer el método, qué argumentos toma y qué devolverá.

Clase

Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 3Veamos la organización interna de las clases. O mejor dicho, algunos puntos de vista y reglas que se deben seguir al construir clases. Normalmente, una clase debería comenzar con una lista de variables, dispuestas en un orden específico:
  1. constantes estáticas públicas;
  2. constantes estáticas privadas;
  3. variables de instancia privada.
A continuación se muestran varios constructores en orden de menos a más argumentos. Les siguen métodos desde el acceso más abierto hasta los más cerrados: por regla general, en la parte inferior se encuentran los métodos privados que ocultan la implementación de alguna funcionalidad que queremos restringir.

Tamaño de la clase

Ahora me gustaría hablar sobre el tamaño de las clases. Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 4Recordemos uno de los principios de SOLID: responsabilidad única . Responsabilidad única : el principio de responsabilidad única. Afirma que cada objeto tiene un solo objetivo (responsabilidad), y la lógica de todos sus métodos está dirigida a garantizarlo. Es decir, en base a esto, debemos evitar clases grandes e infladas (que por su naturaleza son un antipatrón - "objeto divino"), y si tenemos muchos métodos de lógica diversa y heterogénea en una clase, debemos pensar sobre dividirlo en un par de partes lógicas (clases). Esto, a su vez, mejorará la legibilidad del código, ya que no necesitamos mucho tiempo para comprender el propósito de un método si conocemos el propósito aproximado de una clase determinada. También debes estar atento al nombre de la clase : debe reflejar la lógica que contiene. Digamos que si tenemos una clase cuyo nombre tiene más de 20 palabras, debemos pensar en refactorizar. Toda clase que se precie no debería tener una cantidad tan grande de variables internas. De hecho, cada método trabaja con uno de ellos o con varios, lo que provoca un mayor acoplamiento dentro de la clase (que es exactamente lo que debería ser, ya que la clase debe ser como un todo). Como resultado, aumentar la coherencia de una clase conduce a una disminución de ella como tal y, por supuesto, nuestro número de clases aumenta. Para algunos, esto es molesto; necesitan ir más a clase para ver cómo funciona una tarea grande específica. Entre otras cosas, cada clase es un pequeño módulo que debe estar mínimamente conectado con los demás. Este aislamiento reduce la cantidad de cambios que debemos realizar al agregar lógica adicional a una clase.

Objetos

Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 5

Encapsulación

Aquí, en primer lugar, hablaremos sobre uno de los principios de la encapsulación de programación orientada a objetos . Por lo tanto, ocultar la implementación no se reduce a crear una capa de método entre variables (restringir irreflexivamente el acceso a través de métodos únicos, captadores y definidores, lo cual no es bueno, ya que se pierde todo el objetivo de la encapsulación). Ocultar el acceso tiene como objetivo formar abstracciones, es decir, la clase proporciona métodos concretos comunes mediante los cuales trabajamos con nuestros datos. Pero el usuario no necesita saber exactamente cómo trabajamos con estos datos: funciona y está bien.

Ley de Deméter

También puedes considerar la Ley de Demeter: es un pequeño conjunto de reglas que ayudan a gestionar la complejidad a nivel de clase y método. Entonces, supongamos que tenemos un objeto Cary tiene un método: move(Object arg1, Object arg2). Según la Ley de Demeter, este método se limita a llamar:
  • métodos del objeto mismo Car(en otras palabras, esto);
  • métodos de objetos creados en move;
  • métodos de objetos pasados ​​como argumentos - arg1, arg2;
  • métodos de objetos internos Car(lo mismo este).
En otras palabras, la ley de Deméter es algo así como una regla infantil: puedes hablar con amigos, pero no con extraños .

Estructura de datos

Una estructura de datos es una colección de elementos relacionados. Cuando se considera un objeto como una estructura de datos, se trata de un conjunto de elementos de datos que se procesan mediante métodos, cuya existencia está implícita. Es decir, es un objeto cuya finalidad es almacenar y operar (procesar) datos almacenados. La diferencia clave con un objeto normal es que un objeto es un conjunto de métodos que operan sobre elementos de datos cuya existencia está implícita. ¿Lo entiendes? En un objeto normal, lo principal son los métodos, y las variables internas están orientadas a su correcto funcionamiento, pero en una estructura de datos es al revés: los métodos soportan y ayudan a trabajar con los elementos almacenados, que aquí son los principales. Un tipo de estructura de datos es el objeto de transferencia de datos (DTO) . Esta es una clase con variables públicas y sin métodos (o solo métodos de lectura/escritura) que pasan datos cuando se trabaja con bases de datos, trabajan con mensajes de análisis de sockets, etc. Normalmente, los datos en dichos objetos no se almacenan durante mucho tiempo y se se convierte casi de inmediato en la entidad con la que trabaja nuestra aplicación. Una entidad, a su vez, también es una estructura de datos, pero su propósito es participar en la lógica de negocios en diferentes niveles de la aplicación, mientras que el DTO es transportar datos hacia/desde la aplicación. Ejemplo de DTO:
@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Todo parece claro, pero aquí nos enteramos de la existencia de los híbridos. Los híbridos son objetos que contienen métodos para manejar lógica importante y almacenar elementos internos y métodos de acceso (obtener/establecer) a ellos. Estos objetos son confusos y dificultan la adición de nuevos métodos. No vale la pena usarlos, ya que no está claro para qué están destinados: almacenar elementos o realizar algún tipo de lógica. Puedes leer sobre posibles tipos de objetos aquí .

Principios de creación de variables.

Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 6Pensemos un poco en las variables, o mejor dicho, pensemos en cuáles podrían ser los principios para crearlas:
  1. Lo ideal sería declarar e inicializar una variable inmediatamente antes de usarla (en lugar de crearla y olvidarse de ella).
  2. Siempre que sea posible, declare las variables como finales para evitar que su valor cambie después de la inicialización.
  3. No te olvides de las contravariables (normalmente las usamos en algún tipo de bucle for, es decir, no debemos olvidarnos de restablecerlas, de lo contrario puede romper toda nuestra lógica).
  4. Deberías intentar inicializar variables en el constructor.
  5. Si puede elegir entre usar un objeto con o sin referencia ( new SomeObject()), elija sin ( ), ya que este objeto, una vez usado, se eliminará durante la próxima recolección de basura y no desperdiciará recursos.
  6. Hacer que la vida útil de las variables sea lo más corta posible (la distancia entre la creación de una variable y el último acceso).
  7. Inicialice las variables utilizadas en un bucle inmediatamente antes del bucle, en lugar de al principio del método que contiene el bucle.
  8. Comience siempre con el alcance más limitado y amplíelo solo si es necesario (debe intentar que la variable sea lo más local posible).
  9. Utilice cada variable para un solo propósito.
  10. Evite variables con significados ocultos (la variable está dividida entre dos tareas, lo que significa que su tipo no es adecuado para resolver una de ellas).
Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 7

Métodos

Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 8Pasemos directamente a la implementación de nuestra lógica, es decir, a los métodos.
  1. La primera regla es la compacidad. Idealmente, un método no debería exceder las 20 líneas, por lo que si, digamos, un método público "se hincha" significativamente, debe pensar en mover la lógica separada a métodos privados.

  2. La segunda regla es que los bloques de comandos if, etc. no deben estar muy anidados: elseestowhile reduce significativamente la legibilidad del código. Lo ideal es que el anidamiento no supere los dos bloques {}.

    También es recomendable que el código de estos bloques sea compacto y sencillo.

  3. La tercera regla es que un método debe realizar sólo una operación. Es decir, si un método realiza una lógica variada y compleja, lo dividimos en submétodos. Como resultado, el método en sí será una fachada cuyo propósito es llamar a todas las demás operaciones en el orden correcto.

    Pero ¿qué pasa si la operación parece demasiado simple para crear un método separado? Sí, a veces puede parecer como disparar a los gorriones con un cañón, pero los pequeños métodos aportan una serie de beneficios:

    • lectura de código más fácil;
    • los métodos tienden a volverse más complejos a lo largo del desarrollo, y si el método era inicialmente simple, complicar su funcionalidad será un poco más fácil;
    • ocultar detalles de implementación;
    • facilitar la reutilización de código;
    • mayor confiabilidad del código.
  4. La regla descendente es que el código debe leerse de arriba a abajo: cuanto más abajo, mayor será la profundidad de la lógica, y viceversa, cuanto más arriba, más abstractos serán los métodos. Por ejemplo, los comandos de cambio son bastante poco compactos e indeseables, pero si no puede prescindir de usar un interruptor, debe intentar moverlo lo más bajo posible, a los métodos de nivel más bajo.

  5. Argumentos del método : ¿cuántos son ideales? Idealmente, no hay ninguno)) ¿Pero eso realmente sucede? Sin embargo, debes intentar tener el menor número posible, porque cuantos menos haya, más fácil será utilizar este método y más fácil será probarlo. En caso de duda, intente adivinar todos los escenarios para utilizar un método con una gran cantidad de argumentos de entrada.

  6. Por separado, me gustaría resaltar los métodos que tienen una bandera booleana como argumento de entrada , ya que esto naturalmente implica que este método implementa más de una operación (si es verdadero, entonces una, falso, otra). Como escribí anteriormente, esto no es bueno y debe evitarse si es posible.

  7. Si un método tiene una gran cantidad de argumentos entrantes (el valor extremo es 7, pero deberías pensar en ello después de 2-3), debes agrupar algunos argumentos en un objeto separado.

  8. Si hay varios métodos similares (sobrecargados) , entonces se deben pasar parámetros similares en el mismo orden: esto mejora la legibilidad y la usabilidad.

  9. Cuando pasa parámetros a un método, debe estar seguro de que se utilizarán todos; de lo contrario, ¿para qué sirve el argumento? Córtalo de la interfaz y listo.

  10. try/catchNo se ve muy bien por su naturaleza, por lo que una buena medida sería moverlo a un método intermedio separado (método para manejar excepciones):

    public void exceptionHandling(SomeObject obj) {
        try {
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Hablé sobre la repetición de código arriba, pero lo agregaré aquí: si tenemos un par de métodos con partes repetidas del código, debemos moverlo a un método separado, lo que aumentará la compacidad tanto del método como del clase. Y no te olvides de los nombres correctos. Te contaré los detalles sobre la denominación correcta de clases, interfaces, métodos y variables en la siguiente parte del artículo. Y eso es todo para nosotros hoy. Reglas para escribir código: desde la creación de un sistema hasta el trabajo con objetos - 9Reglas del código: el poder de nombrar correctamente, buenos y malos comentarios
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION